improved logging

This commit is contained in:
2026-02-18 10:16:01 -06:00
parent b16fa75706
commit 76c482b84e
8 changed files with 849 additions and 22 deletions

View File

@@ -316,31 +316,19 @@ var nota = use_core('nota')
var ENETSERVICE = 0.1
var REPLYTIMEOUT = 60 // seconds before replies are ignored
function caller_data(depth)
{
return {file: "nofile", line: 0}
}
// --- 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).
function console_rec(line, file, msg) {
return `[${text(_cell.id, 0, 5)}] [${file}:${line}]: ${msg}\n`
// time: [${time.text("mb d yyyy h:nn:ss")}]
}
var log_config = null
var channel_sinks = {}
var wildcard_sinks = []
var warned_channels = {}
function log(name, args) {
var caller = caller_data(1)
var msg = args[0]
if (name == 'console') {
os.print(console_rec(caller.line, caller.file, msg))
} else if (name == 'error') {
if (msg == null) msg = "error"
os.print(console_rec(caller.line, caller.file, msg))
} else if (name == 'system') {
msg = "[SYSTEM] " + msg
os.print(console_rec(caller.line, caller.file, msg))
} else {
log.console(`unknown log type: ${name}`)
}
if (msg == null) msg = ""
os.print(`[${text(_cell.id, 0, 5)}] [${name}]: ${msg}\n`)
}
function actor_die(err)
@@ -428,6 +416,134 @@ core_extras.native_mode = native_mode
var shop = use_core('internal/shop')
if (native_mode) use_core('build')
var time = use_core('time')
var toml = use_core('toml')
// --- Logging system (full version) ---
// Now that toml, time, fd, and json are available, upgrade the log function
// from the bootstrap version to a configurable sink-based system.
function ensure_log_dir(path) {
var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : ''
var i = 0
// ensure parent dir (skip last element which is the filename)
for (i = 0; i < length(parts) - 1; i++) {
if (parts[i] == '') continue
current = current + parts[i] + '/'
if (!fd.is_dir(current)) fd.mkdir(current)
}
}
function build_sink_routing() {
channel_sinks = {}
wildcard_sinks = []
var names = array(log_config.sink)
arrfor(names, function(name) {
var sink = log_config.sink[name]
sink._name = name
if (!is_array(sink.channels)) sink.channels = []
if (is_text(sink.exclude)) sink.exclude = [sink.exclude]
if (!is_array(sink.exclude)) sink.exclude = []
if (sink.type == "file" && sink.path) ensure_log_dir(sink.path)
arrfor(sink.channels, function(ch) {
if (ch == "*") {
wildcard_sinks[] = sink
return
}
if (!channel_sinks[ch]) channel_sinks[ch] = []
channel_sinks[ch][] = sink
})
})
}
function load_log_config() {
var log_path = null
if (shop_path) {
log_path = shop_path + '/log.toml'
if (fd.is_file(log_path)) {
log_config = toml.decode(text(fd.slurp(log_path)))
}
}
if (!log_config || !log_config.sink) {
log_config = {
sink: {
terminal: {
type: "console",
format: "pretty",
channels: ["console", "error", "system"]
}
}
}
}
build_sink_routing()
}
function pretty_format(rec) {
var aid = text(rec.actor_id, 0, 5)
var src = ""
if (rec.source && rec.source.file)
src = rec.source.file + ":" + text(rec.source.line)
var ev = is_text(rec.event) ? rec.event : json.encode(rec.event, false)
return `[${aid}] [${rec.channel}] ${src} ${ev}\n`
}
function bare_format(rec) {
var aid = text(rec.actor_id, 0, 5)
var ev = is_text(rec.event) ? rec.event : json.encode(rec.event, false)
return `[${aid}] ${ev}\n`
}
function sink_excluded(sink, channel) {
var excluded = false
if (!sink.exclude || length(sink.exclude) == 0) return false
arrfor(sink.exclude, function(ex) {
if (ex == channel) excluded = true
})
return excluded
}
function dispatch_to_sink(sink, rec) {
var line = null
if (sink_excluded(sink, rec.channel)) return
if (sink.type == "console") {
if (sink.format == "json")
os.print(json.encode(rec, false) + "\n")
else if (sink.format == "bare")
os.print(bare_format(rec))
else
os.print(pretty_format(rec))
} else if (sink.type == "file") {
line = json.encode(rec, false) + "\n"
fd.slurpappend(sink.path, stone(blob(line)))
}
}
load_log_config()
log = function(name, args) {
var sinks = channel_sinks[name]
var event = args[0]
if (!sinks && length(wildcard_sinks) == 0) {
if (!warned_channels[name]) {
warned_channels[name] = true
os.print(`[warn] log channel '${name}' has no sinks configured\n`)
}
return
}
var caller = caller_info(2)
var rec = {
actor_id: _cell.id,
timestamp: time.number(),
channel: name,
event: event,
source: caller
}
if (sinks) arrfor(sinks, function(sink) { dispatch_to_sink(sink, rec) })
arrfor(wildcard_sinks, function(sink) { dispatch_to_sink(sink, rec) })
}
var pronto = use_core('pronto')
var fallback = pronto.fallback
@@ -438,6 +554,7 @@ var sequence = pronto.sequence
runtime_env.actor = actor
runtime_env.log = log
runtime_env.send = send
runtime_env.shop_path = shop_path
runtime_env.fallback = fallback
runtime_env.parallel = parallel
runtime_env.race = race