From 148cf12787ec0bd495105c98a7361146d091ff43 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 20 Feb 2026 15:03:58 -0600 Subject: [PATCH] fixed logging and remove --- docs/cli.md | 2 ++ docs/logging.md | 25 ++++++++++++++++++------- internal/engine.cm | 24 +++++++++++++++++++++++- log.ce | 32 ++++++++++++++++++++------------ remove.ce | 2 ++ 5 files changed, 65 insertions(+), 20 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index 907b1d6d..af10caf8 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -317,11 +317,13 @@ Options: - `--format=pretty|bare|json` — output format (default: `pretty` for console, `json` for file) - `--channels=ch1,ch2` — channels to subscribe (default: `console,error,system`). Use `'*'` for all channels (quote to prevent shell glob expansion). - `--exclude=ch1,ch2` — channels to exclude (useful with `'*'`) +- `--stack=ch1,ch2` — channels that capture a full stack trace (default: `error`) ```bash pit log add terminal console --format=bare --channels=console pit log add errors file .cell/logs/errors.jsonl --channels=error 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 diff --git a/docs/logging.md b/docs/logging.md index 2aea9e42..b7b0d838 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -29,14 +29,16 @@ 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: +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] main.ce:5 server started on port 8080 -[a3f12] [error] main.ce:12 connection refused +[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] file:line message`. +The format is `[actor_id] [channel] message`. Error stack traces are always on unless you explicitly configure a sink without them. ## Configuration @@ -114,14 +116,22 @@ File sinks write one JSON-encoded record per line. Console sinks format the reco ## 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. +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: + +```bash +pit log add terminal console --channels=console,error,debug --stack=error,debug +``` + +Or in `log.toml`: ```toml [sink.terminal] type = "console" format = "bare" -channels = ["console", "error"] -stack = ["error"] +channels = ["console", "error", "debug"] +stack = ["error", "debug"] ``` Only channels listed in `stack` get stack traces. Other channels on the same sink print without one: @@ -150,6 +160,7 @@ The `pit log` command manages sinks and reads log files. See [CLI — pit log](/ 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 diff --git a/internal/engine.cm b/internal/engine.cm index eaf62fb7..2b77b6c5 100644 --- a/internal/engine.cm +++ b/internal/engine.cm @@ -506,20 +506,37 @@ var REPLYTIMEOUT = 60 // seconds before replies are ignored // --- Logging system (bootstrap phase) --- // Early log: prints to console before toml/time/json are loaded. // Upgraded to full sink-based system after config loads (see load_log_config below). +// The bootstrap log forwards to _log_full once the full system is ready, so that +// modules loaded early (like shop.cm) get full logging even though they captured +// the bootstrap function reference. var log_config = null var channel_sinks = {} var wildcard_sinks = [] var warned_channels = {} var stack_channels = {} +var _log_full = null var log_quiet_channels = { shop: true } function log(name, args) { + if (_log_full) return _log_full(name, args) if (log_quiet_channels[name]) return var msg = args[0] + var stk = null + var i = 0 + var fr = null if (msg == null) msg = "" os.print(`[${text(_cell.id, 0, 5)}] [${name}]: ${msg}\n`) + if (name == "error") { + stk = os.stack(2) + if (stk && length(stk) > 0) { + for (i = 0; i < length(stk); i = i + 1) { + fr = stk[i] + os.print(` at ${fr.fn} (${fr.file}:${text(fr.line)}:${text(fr.col)})\n`) + } + } + } } function actor_die(err) @@ -648,6 +665,7 @@ function build_sink_routing() { var names = array(log_config.sink) arrfor(names, function(name) { var sink = log_config.sink[name] + if (!sink || !is_object(sink)) return sink._name = name if (!is_array(sink.channels)) sink.channels = [] if (is_text(sink.exclude)) sink.exclude = [sink.exclude] @@ -677,7 +695,7 @@ function load_log_config() { log_config = toml.decode(text(fd.slurp(log_path))) } } - if (!log_config || !log_config.sink) { + if (!log_config || !log_config.sink || length(array(log_config.sink)) == 0) { log_config = { sink: { terminal: { @@ -787,6 +805,10 @@ log = function(name, args) { // Wire C-level JS_Log through the ƿit log system actor_mod.set_log(log) +// Let the bootstrap log forward to the full system — modules loaded early +// (before the full log was ready) captured the bootstrap function reference. +_log_full = log + var pronto = use_core('pronto') var fallback = pronto.fallback var parallel = pronto.parallel diff --git a/log.ce b/log.ce index 8428a7a5..7caac4f3 100644 --- a/log.ce +++ b/log.ce @@ -1,12 +1,15 @@ // cell log - Manage and read log sinks // // Usage: -// cell log list List configured sinks -// cell log add console [opts] Add a console sink +// cell log list List configured sinks +// cell log add console [opts] Add a console sink // cell log add file [opts] Add a file sink -// cell log remove Remove a sink -// cell log read [opts] Read from a file sink -// cell log tail [--lines=N] Follow a file sink +// cell log remove Remove a sink +// cell log read [opts] Read from a file sink +// cell log tail [--lines=N] Follow a file sink +// +// The --stack option controls which channels capture a stack trace. +// Default: --stack=error (errors always show a stack trace). var toml = use('toml') var fd = use('fd') @@ -53,6 +56,7 @@ function print_help() { log.console(" --format=pretty|bare|json Output format (default: pretty for console, json for file)") log.console(" --channels=ch1,ch2 Channels to subscribe (default: console,error,system)") log.console(" --exclude=ch1,ch2 Channels to exclude (for wildcard sinks)") + log.console(" --stack=ch1,ch2 Channels that capture a stack trace (default: error)") log.console("") log.console("Options for read:") log.console(" --lines=N Show last N lines (default: all)") @@ -80,21 +84,22 @@ function format_entry(entry) { function do_list() { var config = load_config() var names = null - if (!config || !config.sink) { + names = (config && config.sink) ? array(config.sink) : [] + if (length(names) == 0) { log.console("No log sinks configured.") - log.console("Default: console pretty for console/error/system") + log.console("Default: console pretty for console/error/system (stack traces on error)") return } - names = array(config.sink) arrfor(names, function(n) { var s = config.sink[n] var ch = is_array(s.channels) ? text(s.channels, ', ') : '(none)' var ex = is_array(s.exclude) ? " exclude=" + text(s.exclude, ',') : "" + var stk = is_array(s.stack) ? " stack=" + text(s.stack, ',') : "" var fmt = s.format || (s.type == 'file' ? 'json' : 'pretty') if (s.type == 'file') - log.console(" " + n + ": " + s.type + " -> " + s.path + " [" + ch + "] format=" + fmt + ex) + log.console(" " + n + ": " + s.type + " -> " + s.path + " [" + ch + "] format=" + fmt + ex + stk) else - log.console(" " + n + ": " + s.type + " [" + ch + "] format=" + fmt + ex) + log.console(" " + n + ": " + s.type + " [" + ch + "] format=" + fmt + ex + stk) }) } @@ -105,6 +110,7 @@ function do_add() { var format = null var channels = ["console", "error", "system"] var exclude = null + var stack_chs = ["error"] var config = null var val = null var i = 0 @@ -138,13 +144,15 @@ function do_add() { if (val) { channels = array(val, ','); continue } val = parse_opt(args[i], 'exclude') if (val) { exclude = array(val, ','); continue } + val = parse_opt(args[i], 'stack') + if (val) { stack_chs = array(val, ','); continue } } config = load_config() if (!config) config = {} if (!config.sink) config.sink = {} - config.sink[name] = {type: sink_type, format: format, channels: channels} + config.sink[name] = {type: sink_type, format: format, channels: channels, stack: stack_chs} if (path) config.sink[name].path = path if (exclude) config.sink[name].exclude = exclude @@ -165,7 +173,7 @@ function do_remove() { log.error("Sink not found: " + name) return } - config.sink[name] = null + delete config.sink[name] save_config(config) log.console("Removed sink: " + name) } diff --git a/remove.ce b/remove.ce index 09f6d76d..c156da71 100644 --- a/remove.ce +++ b/remove.ce @@ -33,6 +33,7 @@ for (i = 0; i < length(args); i++) { log.console(" --prune Also remove packages no longer needed by any root") log.console(" --dry-run Show what would be removed") $stop() + disrupt } else if (!starts_with(args[i], '-')) { target_pkg = args[i] } @@ -41,6 +42,7 @@ for (i = 0; i < length(args); i++) { if (!target_pkg) { log.console("Usage: cell remove [options]") $stop() + disrupt } // Resolve relative paths to absolute paths