--- title: "Logging" description: "Configurable channel-based logging with sinks" weight: 25 type: "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. ```javascript 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: ``` [a3f12] [console] main.ce:5 server started on port 8080 [a3f12] [error] main.ce:12 connection refused ``` The format is `[actor_id] [channel] file:line message`. ## Configuration Logging is configured in `.cell/log.toml`. Each `[sink.NAME]` section defines a sink. ```toml [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. ```json {"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: ```javascript { 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 Add a `stack` field to a sink to capture a full call stack for specific channels. The value is an array of channel names. ```toml [sink.terminal] type = "console" format = "bare" channels = ["console", "error"] stack = ["error"] ``` 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: ```json {"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](/docs/cli/#pit-log) for the full reference. ```bash 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 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. ```toml [sink.terminal] type = "console" format = "bare" channels = ["console"] [sink.debug] type = "file" path = ".cell/logs/debug.jsonl" channels = ["*"] exclude = ["console"] ``` ```javascript 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. ```toml [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. ```toml [sink.json_out] type = "console" format = "json" channels = ["console", "error"] ``` ```bash pit run myapp.ce | jq '.event' ``` ### Reading logs ```bash # 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 ```