Merge branch 'improve_compile_error' into audit_dups

This commit is contained in:
2026-02-20 15:07:51 -06:00
5 changed files with 113 additions and 70 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

32
log.ce
View File

@@ -1,12 +1,15 @@
// cell log - Manage and read log sinks
//
// Usage:
// cell log list List configured sinks
// cell log add <name> console [opts] Add a console sink
// cell log list List configured sinks
// cell log add <name> console [opts] Add a console sink
// cell log add <name> file <path> [opts] Add a file sink
// cell log remove <name> Remove a sink
// cell log read <sink> [opts] Read from a file sink
// cell log tail <sink> [--lines=N] Follow a file sink
// cell log remove <name> Remove a sink
// cell log read <sink> [opts] Read from a file sink
// cell log tail <sink> [--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)
}

100
remove.ce
View File

@@ -43,64 +43,64 @@ var run = function() {
return
}
target_pkg = shop.resolve_locator(target_pkg)
target_pkg = shop.resolve_locator(target_pkg)
var packages_to_remove = [target_pkg]
var packages_to_remove = [target_pkg]
var lock = null
var all_packages = null
var needed = null
if (prune) {
// Find packages no longer needed
// Get all dependencies of remaining packages
lock = shop.load_lock()
all_packages = shop.list_packages()
var lock = null
var all_packages = null
var needed = null
if (prune) {
// Find packages no longer needed
// Get all dependencies of remaining packages
lock = shop.load_lock()
all_packages = shop.list_packages()
// Build set of all needed packages (excluding target)
needed = {}
arrfor(all_packages, function(p) {
if (p == target_pkg || p == 'core') return
// Build set of all needed packages (excluding target)
needed = {}
arrfor(all_packages, function(p) {
if (p == target_pkg || p == 'core') return
// Mark this package and its deps as needed
needed[p] = true
var _gather = function() {
var deps = pkg.gather_dependencies(p)
arrfor(deps, function(dep) {
needed[dep] = true
})
} disruption {
// Skip if can't read deps
}
_gather()
})
// Mark this package and its deps as needed
needed[p] = true
var _gather = function() {
var deps = pkg.gather_dependencies(p)
arrfor(deps, function(dep) {
needed[dep] = true
})
} disruption {
// Skip if can't read deps
}
_gather()
})
// Find packages that are NOT needed
arrfor(all_packages, function(p) {
if (p == 'core') return
if (!needed[p] && find(packages_to_remove, p) == null) {
push(packages_to_remove, p)
}
})
}
// Find packages that are NOT needed
arrfor(all_packages, function(p) {
if (p == 'core') return
if (!needed[p] && find(packages_to_remove, p) == null) {
push(packages_to_remove, p)
}
})
}
if (dry_run) {
log.console("Would remove:")
arrfor(packages_to_remove, function(p) {
log.console(" " + p)
})
} else {
arrfor(packages_to_remove, function(p) {
// Remove any link for this package
if (link.is_linked(p)) {
link.remove(p)
}
if (dry_run) {
log.console("Would remove:")
arrfor(packages_to_remove, function(p) {
log.console(" " + p)
})
} else {
arrfor(packages_to_remove, function(p) {
// Remove any link for this package
if (link.is_linked(p)) {
link.remove(p)
}
// Remove from shop
shop.remove(p)
})
// Remove from shop
shop.remove(p)
})
log.console("Removed " + text(length(packages_to_remove)) + " package(s).")
}
log.console("Removed " + text(length(packages_to_remove)) + " package(s).")
}
}
run()