6.3 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
Three channels are conventional:
| Channel | Usage |
|---|---|
log.console(msg) |
General output |
log.error(msg) |
Errors and warnings |
log.system(msg) |
Internal system messages |
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, error, and system to the terminal in pretty format. The error channel includes a stack trace by default:
[a3f12] [console] server started on port 8080
[a3f12] [error] connection refused
at handle_request (server.ce:42:3)
at main (main.ce:5:1)
The format is [actor_id] [channel] message. Error stack traces are always on unless you explicitly configure a sink without them.
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 |
"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
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 add terminal console --format=bare --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
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