diff --git a/build.cm b/build.cm index 86b84f3a..e96fac3f 100644 --- a/build.cm +++ b/build.cm @@ -464,7 +464,7 @@ Build.compile_file = function(pkg, file, target, opts) { // Compile log.shop('compiling ' + file) - log.console('Compiling ' + file) + log.build('Compiling ' + file) err_path = '/tmp/cell_build_err_' + content_hash(setup.src_path) + '.log' full_cmd = setup.cmd_str + ' -o "' + obj_path + '" 2>"' + err_path + '"' ret = os.system(full_cmd) @@ -603,6 +603,8 @@ Build.build_module_dylib = function(pkg, file, target, opts) { var post_probe = null var fallback_probe = null var _fail_msg2 = null + var link_err_path = null + var link_err_text = null if (probe && probe.fail) { _fail_msg2 = probe.fail_path ? text(fd.slurp(probe.fail_path)) : null @@ -697,10 +699,16 @@ Build.build_module_dylib = function(pkg, file, target, opts) { cmd_str = text(cmd_parts, ' ') if (_opts.verbose) log.build('[verbose] link: ' + cmd_str) log.shop('linking ' + file) - log.console('Linking module ' + file + ' -> ' + fd.basename(dylib_path)) - ret = os.system(cmd_str) + log.build('Linking module ' + file + ' -> ' + fd.basename(dylib_path)) + link_err_path = '/tmp/cell_link_err_' + content_hash(file) + '.log' + ret = os.system(cmd_str + ' 2>"' + link_err_path + '"') if (ret != 0) { - log.error('Linking failed: ' + file) + if (fd.is_file(link_err_path)) + link_err_text = text(fd.slurp(link_err_path)) + if (link_err_text) + log.error('Linking failed: ' + file + '\n' + link_err_text) + else + log.error('Linking failed: ' + file) return null } @@ -745,6 +753,9 @@ Build.build_dynamic = function(pkg, target, buildtype, opts) { var _opts = opts || {} var c_files = pkg_tools.get_c_files(pkg, _target, true) var results = [] + var total = length(c_files) + var done = 0 + var failed = 0 // Pre-fetch cflags once to avoid repeated TOML reads var pkg_dir = shop.get_package_dir(pkg) @@ -760,14 +771,24 @@ Build.build_dynamic = function(pkg, target, buildtype, opts) { }) } + if (total > 0) + os.print(' Building C modules ') + arrfor(c_files, function(file) { var sym_name = shop.c_symbol_for_file(pkg, file) var dylib = Build.build_module_dylib(pkg, file, _target, {buildtype: _buildtype, extra_objects: support_objects, cflags: cached_cflags, verbose: _opts.verbose, force: _opts.force}) if (dylib) { push(results, {file: file, symbol: sym_name, dylib: dylib}) + } else { + failed = failed + 1 } + done = done + 1 + os.print('.') }) + if (total > 0) + os.print(` ${text(done)}/${text(total)}${failed > 0 ? `, ${text(failed)} failed` : ''}\n`) + // Write manifest so runtime can find dylibs without the build module var mpath = manifest_path(pkg) fd.slurpwrite(mpath, stone(blob(json.encode(results)))) diff --git a/docs/logging.md b/docs/logging.md index b7b0d838..b0bca1f1 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -9,13 +9,16 @@ Logging in ƿit is channel-based. Any `log.X(value)` call writes to channel `"X" ## Channels -Three channels are conventional: +These channels are conventional: | Channel | Usage | |---------|-------| | `log.console(msg)` | General output | -| `log.error(msg)` | Errors and warnings | +| `log.error(msg)` | Errors | +| `log.warn(msg)` | Compiler diagnostics and warnings | | `log.system(msg)` | Internal system messages | +| `log.build(msg)` | Per-file compile/link output | +| `log.shop(msg)` | Package management internals | Any name works. `log.debug(msg)` creates channel `"debug"`, `log.perf(msg)` creates `"perf"`, and so on. @@ -29,16 +32,18 @@ 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: +With no configuration, a default sink routes `console` and `error` to the terminal in clean format. The `error` channel includes a stack trace by default: ``` -[a3f12] [console] server started on port 8080 -[a3f12] [error] connection refused +server started on port 8080 +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. +Clean format prints messages without actor ID or channel prefix. Error messages are prefixed with `error:`. Other formats (`pretty`, `bare`) include actor IDs and are available for debugging. Stack traces are always on for errors unless you explicitly configure a sink without them. + +Channels like `warn`, `build`, and `shop` are not routed to the terminal by default. Enable them with `pit log enable `. ## Configuration @@ -67,7 +72,7 @@ exclude = ["console"] | Field | Values | Description | |-------|--------|-------------| | `type` | `"console"`, `"file"` | Where output goes | -| `format` | `"pretty"`, `"bare"`, `"json"` | How output is formatted | +| `format` | `"clean"`, `"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 | @@ -75,6 +80,13 @@ exclude = ["console"] ### Formats +**clean** — CLI-friendly. No actor ID or channel prefix. Error channel messages are prefixed with `error:`. This is the default format. + +``` +server started on port 8080 +error: connection refused +``` + **pretty** — human-readable, one line per message. Includes actor ID, channel, source location, and message. ``` @@ -158,7 +170,10 @@ The `pit log` command manages sinks and reads log files. See [CLI — pit log](/ ```bash pit log list # show sinks -pit log add terminal console --format=bare --channels=console +pit log channels # list channels with enabled/disabled status +pit log enable warn # enable a channel on the terminal sink +pit log disable warn # disable a channel +pit log add terminal console --format=clean --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 @@ -166,6 +181,16 @@ pit log read dump --lines=20 --channel=error pit log tail dump ``` +### Channel toggling + +The `enable` and `disable` commands modify the terminal sink's channel list without touching other sink configuration. This is the easiest way to opt in to extra output: + +```bash +pit log enable warn # see compiler warnings +pit log enable build # see per-file compile/link output +pit log disable warn # hide warnings again +``` + ## Examples ### Development setup diff --git a/internal/bootstrap.cm b/internal/bootstrap.cm index a2d13395..e77517b4 100644 --- a/internal/bootstrap.cm +++ b/internal/bootstrap.cm @@ -259,7 +259,6 @@ if (_native) { compile_native_cached(_te.name, core_path + '/' + _te.path) _ti = _ti + 1 } - os.print("bootstrap: native cache seeded\n") } else { // Bytecode path: seed cache with everything engine needs _targets = [ @@ -276,5 +275,4 @@ if (_native) { compile_and_cache(_te.name, core_path + '/' + _te.path) _ti = _ti + 1 } - os.print("bootstrap: cache seeded\n") } diff --git a/internal/engine.cm b/internal/engine.cm index 4c1b5b3c..ce0d25f3 100644 --- a/internal/engine.cm +++ b/internal/engine.cm @@ -285,7 +285,7 @@ function analyze(src, filename) { _i = 0 while (_i < length(folded._diagnostics)) { e = folded._diagnostics[_i] - os.print(`${filename}:${text(e.line)}:${text(e.col)}: ${e.severity}: ${e.message}\n`) + log.warn(`${filename}:${text(e.line)}:${text(e.col)}: ${e.severity}: ${e.message}`) _i = _i + 1 } if (_wm) { @@ -443,8 +443,12 @@ function run_ast_fn(name, ast, env, pkg) { _has_errors = false while (_di < length(optimized._diagnostics)) { _diag = optimized._diagnostics[_di] - os.print(`${_diag.file}:${text(_diag.line)}:${text(_diag.col)}: ${_diag.severity}: ${_diag.message}\n`) - if (_diag.severity == "error") _has_errors = true + if (_diag.severity == "error") { + log.error(`${_diag.file}:${text(_diag.line)}:${text(_diag.col)}: ${_diag.severity}: ${_diag.message}`) + _has_errors = true + } else { + log.warn(`${_diag.file}:${text(_diag.line)}:${text(_diag.col)}: ${_diag.severity}: ${_diag.message}`) + } _di = _di + 1 } if (_has_errors) disrupt @@ -506,8 +510,12 @@ function compile_user_blob(name, ast, pkg) { _has_errors = false while (_di < length(optimized._diagnostics)) { _diag = optimized._diagnostics[_di] - os.print(`${_diag.file}:${text(_diag.line)}:${text(_diag.col)}: ${_diag.severity}: ${_diag.message}\n`) - if (_diag.severity == "error") _has_errors = true + if (_diag.severity == "error") { + log.error(`${_diag.file}:${text(_diag.line)}:${text(_diag.col)}: ${_diag.severity}: ${_diag.message}`) + _has_errors = true + } else { + log.warn(`${_diag.file}:${text(_diag.line)}:${text(_diag.col)}: ${_diag.severity}: ${_diag.message}`) + } _di = _di + 1 } if (_has_errors) disrupt @@ -920,9 +928,8 @@ function load_log_config() { sink: { terminal: { type: "console", - format: "pretty", - channels: ["*"], - exclude: ["system", "shop", "build"], + format: "clean", + channels: ["console", "error"], stack: ["error"] } } @@ -964,6 +971,25 @@ function bare_format(rec) { return out } +function clean_format(rec) { + var ev = is_text(rec.event) ? rec.event : json.encode(rec.event, false) + var out = null + var i = 0 + var fr = null + if (rec.channel == "error") { + out = `error: ${ev}\n` + } else { + out = `${ev}\n` + } + if (rec.stack && length(rec.stack) > 0) { + for (i = 0; i < length(rec.stack); i = i + 1) { + fr = rec.stack[i] + out = out + ` at ${fr.fn} (${fr.file}:${text(fr.line)}:${text(fr.col)})\n` + } + } + return out +} + function sink_excluded(sink, channel) { var excluded = false if (!sink.exclude || length(sink.exclude) == 0) return false @@ -981,6 +1007,8 @@ function dispatch_to_sink(sink, rec) { os.print(json.encode(rec, false) + "\n") else if (sink.format == "bare") os.print(bare_format(rec)) + else if (sink.format == "clean") + os.print(clean_format(rec)) else os.print(pretty_format(rec)) } else if (sink.type == "file") { diff --git a/internal/shop.cm b/internal/shop.cm index 6aa9d62a..1f98c20c 100644 --- a/internal/shop.cm +++ b/internal/shop.cm @@ -1882,6 +1882,7 @@ Shop.sync_with_deps = function(pkg, opts) { if (visited[current]) continue visited[current] = true + log.console(' Fetching ' + current + '...') Shop.sync(current, opts) _read_deps = function() { @@ -2072,6 +2073,12 @@ Shop.build_package_scripts = function(package) _try() }) + if (length(errors) > 0) { + log.console(' Compiling scripts (' + text(ok) + ' ok, ' + text(length(errors)) + ' errors)') + } else if (ok > 0) { + log.console(' Compiling scripts (' + text(ok) + ' ok)') + } + return {ok: ok, errors: errors, total: length(scripts)} } diff --git a/log.ce b/log.ce index 7caac4f3..9e06ecc2 100644 --- a/log.ce +++ b/log.ce @@ -2,6 +2,9 @@ // // Usage: // cell log list List configured sinks +// cell log channels List channels with status +// cell log enable Enable a channel on terminal +// cell log disable Disable a channel on terminal // cell log add console [opts] Add a console sink // cell log add file [opts] Add a file sink // cell log remove Remove a sink @@ -46,6 +49,9 @@ function print_help() { log.console("") log.console("Commands:") log.console(" list List configured sinks") + log.console(" channels List channels with status") + log.console(" enable Enable a channel on terminal") + log.console(" disable Disable a channel on terminal") log.console(" add console [opts] Add a console sink") log.console(" add file [opts] Add a file sink") log.console(" remove Remove a sink") @@ -328,6 +334,128 @@ function do_tail() { poll() } +var known_channels = ["console", "error", "warn", "system", "build", "shop", "compile", "test"] + +function find_terminal_sink(config) { + var names = null + var found = null + if (!config || !config.sink) return null + names = array(config.sink) + if (config.sink.terminal) return config.sink.terminal + arrfor(names, function(n) { + if (!found && config.sink[n].type == "console") + found = config.sink[n] + }) + return found +} + +function do_enable() { + var channel = null + var config = null + var sink = null + var i = 0 + var already = false + if (length(args) < 2) { + log.error("Usage: cell log enable ") + return + } + channel = args[1] + config = load_config() + if (!config) config = {sink: {}} + if (!config.sink) config.sink = {} + sink = find_terminal_sink(config) + if (!sink) { + config.sink.terminal = {type: "console", format: "clean", channels: ["console", "error", channel], stack: ["error"]} + save_config(config) + log.console("Enabled channel: " + channel) + return + } + if (is_array(sink.channels) && length(sink.channels) == 1 && sink.channels[0] == "*") { + if (is_array(sink.exclude)) { + var new_exclude = [] + arrfor(sink.exclude, function(ex) { + if (ex != channel) push(new_exclude, ex) + }) + sink.exclude = new_exclude + } + } else { + if (!is_array(sink.channels)) sink.channels = ["console", "error"] + arrfor(sink.channels, function(ch) { + if (ch == channel) already = true + }) + if (!already) sink.channels[] = channel + } + save_config(config) + log.console("Enabled channel: " + channel) +} + +function do_disable() { + var channel = null + var config = null + var sink = null + var i = 0 + var new_channels = [] + if (length(args) < 2) { + log.error("Usage: cell log disable ") + return + } + channel = args[1] + config = load_config() + if (!config || !config.sink) { + log.error("No log configuration found") + return + } + sink = find_terminal_sink(config) + if (!sink) { + log.error("No terminal sink found") + return + } + if (is_array(sink.channels) && length(sink.channels) == 1 && sink.channels[0] == "*") { + if (!is_array(sink.exclude)) sink.exclude = [] + var already_excluded = false + arrfor(sink.exclude, function(ex) { + if (ex == channel) already_excluded = true + }) + if (!already_excluded) sink.exclude[] = channel + } else { + if (is_array(sink.channels)) { + arrfor(sink.channels, function(ch) { + if (ch != channel) push(new_channels, ch) + }) + sink.channels = new_channels + } + } + save_config(config) + log.console("Disabled channel: " + channel) +} + +function do_channels() { + var config = load_config() + var sink = null + var is_wildcard = false + var active = {} + if (config) sink = find_terminal_sink(config) + if (sink) { + if (is_array(sink.channels) && length(sink.channels) == 1 && sink.channels[0] == "*") { + is_wildcard = true + arrfor(known_channels, function(ch) { active[ch] = true }) + if (is_array(sink.exclude)) { + arrfor(sink.exclude, function(ex) { active[ex] = false }) + } + } else if (is_array(sink.channels)) { + arrfor(sink.channels, function(ch) { active[ch] = true }) + } + } else { + active.console = true + active.error = true + } + log.console("Channels:") + arrfor(known_channels, function(ch) { + var status = active[ch] ? "enabled" : "disabled" + log.console(" " + ch + ": " + status) + }) +} + // Main dispatch if (length(args) == 0) { print_help() @@ -335,6 +463,12 @@ if (length(args) == 0) { print_help() } else if (args[0] == 'list') { do_list() +} else if (args[0] == 'channels') { + do_channels() +} else if (args[0] == 'enable') { + do_enable() +} else if (args[0] == 'disable') { + do_disable() } else if (args[0] == 'add') { do_add() } else if (args[0] == 'remove') { diff --git a/source/runtime.c b/source/runtime.c index c1cb3c2f..7b74a869 100644 --- a/source/runtime.c +++ b/source/runtime.c @@ -3277,7 +3277,8 @@ JS_RaiseDisrupt (JSContext *ctx, const char *fmt, ...) { va_start (ap, fmt); vsnprintf (buf, sizeof (buf), fmt, ap); va_end (ap); - JS_Log (ctx, "error", "%s", buf); + if (ctx->log_callback) + JS_Log (ctx, "error", "%s", buf); ctx->current_exception = JS_TRUE; return JS_EXCEPTION; }