Files
cell/docs/logging.md

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