7.5 KiB
title, description, weight, type
| title | description | weight | type |
|---|---|---|---|
| Logging | Configurable channel-based logging with sinks | 25 | docs |
Logging in ƿit is channel-based. Any log.X(value) call writes to channel "X". Channels are routed to sinks — named destinations that format and deliver log output to the console or to files.
Channels
These channels are conventional:
| Channel | Usage |
|---|---|
log.console(msg) |
General output |
log.error(msg) |
Errors |
log.warn(msg) |
Compiler diagnostics and warnings |
log.system(msg) |
Internal system messages |
log.build(msg) |
Per-file compile/link output |
log.shop(msg) |
Package management internals |
Any name works. log.debug(msg) creates channel "debug", log.perf(msg) creates "perf", and so on.
log.console("server started on port 8080")
log.error("connection refused")
log.debug({query: "SELECT *", rows: 42})
Non-text values are JSON-encoded automatically.
Default Behavior
With no configuration, a default sink routes console and error to the terminal in clean format. The error channel includes a stack trace by default:
server started on port 8080
error: connection refused
at handle_request (server.ce:42:3)
at main (main.ce:5:1)
Clean format prints messages without actor ID or channel prefix. Error messages are prefixed with error:. Other formats (pretty, bare) include actor IDs and are available for debugging. Stack traces are always on for errors unless you explicitly configure a sink without them.
Channels like warn, build, and shop are not routed to the terminal by default. Enable them with pit log enable <channel>.
Configuration
Logging is configured in .cell/log.toml. Each [sink.NAME] section defines a sink.
[sink.terminal]
type = "console"
format = "bare"
channels = ["console"]
[sink.errors]
type = "file"
path = ".cell/logs/errors.jsonl"
channels = ["error"]
[sink.everything]
type = "file"
path = ".cell/logs/all.jsonl"
channels = ["*"]
exclude = ["console"]
Sink fields
| Field | Values | Description |
|---|---|---|
type |
"console", "file" |
Where output goes |
format |
"clean", "pretty", "bare", "json" |
How output is formatted |
channels |
array of names, or ["*"] |
Which channels this sink receives. Quote '*' on the CLI to prevent shell glob expansion. |
exclude |
array of names | Channels to skip (useful with "*") |
stack |
array of channel names | Channels that capture a stack trace |
path |
file path | Output file (file sinks only) |
Formats
clean — CLI-friendly. No actor ID or channel prefix. Error channel messages are prefixed with error:. This is the default format.
server started on port 8080
error: connection refused
pretty — human-readable, one line per message. Includes actor ID, channel, source location, and message.
[a3f12] [console] main.ce:5 server started
bare — minimal. Actor ID and message only.
[a3f12] server started
json — structured JSONL (one JSON object per line). Used for file sinks and machine consumption.
{"actor_id":"a3f12...","timestamp":1702656000.5,"channel":"console","event":"server started","source":{"file":"main.ce","line":5,"col":3,"fn":"init"}}
Log Records
Every log call produces a record:
{
actor_id: "a3f12...", // full actor GUID
timestamp: 1702656000.5, // seconds since epoch
channel: "console", // channel name
event: "the message", // value passed to log
source: {
file: "main.ce",
line: 5,
col: 3,
fn: "init"
}
}
File sinks write one JSON-encoded record per line. Console sinks format the record according to their format setting.
Stack Traces
The error channel captures stack traces by default. To enable stack traces for other channels, add a stack field to a sink — an array of channel names that should include a call stack.
Via the CLI:
pit log add terminal console --channels=console,error,debug --stack=error,debug
Or in log.toml:
[sink.terminal]
type = "console"
format = "bare"
channels = ["console", "error", "debug"]
stack = ["error", "debug"]
Only channels listed in stack get stack traces. Other channels on the same sink print without one:
[a3f12] server started
[a3f12] connection failed
at handle_request (server.ce:42:3)
at process (router.ce:18:5)
at main (main.ce:5:1)
With JSON format, a stack array is added to the record for channels that have stack capture enabled:
{"actor_id":"a3f12...","channel":"error","event":"connection failed","source":{"file":"server.ce","line":42,"col":3,"fn":"handle_request"},"stack":[{"fn":"handle_request","file":"server.ce","line":42,"col":3},{"fn":"process","file":"router.ce","line":18,"col":5},{"fn":"main","file":"main.ce","line":5,"col":1}]}
Channels without stack configuration produce no stack field. Capturing stacks adds overhead — enable it for debugging, not production.
CLI
The pit log command manages sinks and reads log files. See CLI — pit log for the full reference.
pit log list # show sinks
pit log channels # list channels with enabled/disabled status
pit log enable warn # enable a channel on the terminal sink
pit log disable warn # disable a channel
pit log add terminal console --format=clean --channels=console
pit log add dump file .cell/logs/dump.jsonl '--channels=*' --exclude=console
pit log add debug console --channels=error,debug --stack=error,debug
pit log remove terminal
pit log read dump --lines=20 --channel=error
pit log tail dump
Channel toggling
The enable and disable commands modify the terminal sink's channel list without touching other sink configuration. This is the easiest way to opt in to extra output:
pit log enable warn # see compiler warnings
pit log enable build # see per-file compile/link output
pit log disable warn # hide warnings again
Examples
Development setup
Route console output to the terminal with minimal formatting. Send everything else to a structured log file for debugging.
[sink.terminal]
type = "console"
format = "bare"
channels = ["console"]
[sink.debug]
type = "file"
path = ".cell/logs/debug.jsonl"
channels = ["*"]
exclude = ["console"]
log.console("listening on :8080") // -> terminal: [a3f12] listening on :8080
log.error("bad request") // -> debug.jsonl only
log.debug({latency: 0.042}) // -> debug.jsonl only
Separate error log
Keep a dedicated error log alongside a full dump.
[sink.terminal]
type = "console"
format = "pretty"
channels = ["console", "error", "system"]
[sink.errors]
type = "file"
path = ".cell/logs/errors.jsonl"
channels = ["error"]
[sink.all]
type = "file"
path = ".cell/logs/all.jsonl"
channels = ["*"]
JSON console
Output structured JSON to the console for piping into other tools.
[sink.json_out]
type = "console"
format = "json"
channels = ["console", "error"]
pit run myapp.ce | jq '.event'
Reading logs
# Last 50 error entries
pit log read errors --lines=50
# Errors since a timestamp
pit log read errors --since=1702656000
# Filter a wildcard sink to one channel
pit log read all --channel=debug --lines=10
# Follow a log file in real time
pit log tail all