New basic logging module using kx.log as reference#90
Conversation
| {[x;y] string .z.h}; | ||
| {[x;y] y}; | ||
| {[x;y] string .z.M.syslogLvl`$lower x}; | ||
| {[x;y] "$"}); |
There was a problem hiding this comment.
.z.M.syslogLvl references the module namespace dictionary's syslogLvl key, but syslogLvl is a plain name defined at the top level of the file (line 12). At the time the pattern dict is evaluated, .z.M may not yet be set (it is typically assigned by the module loader after the file is loaded), so this lookup will fail with a rank/type error or silently return null. Access the file-level name directly as syslogLvl instead of .z.M.syslogLvl.
| {[id;gv;lvl;msg] | ||
| if[(lvls?lvl)<lvls?gv`lvl;:()]; | ||
| txt:fmtapply[gv`prep;upper string lvl;fmtmsg msg],nl; | ||
| {[txt;pair] |
There was a problem hiding this comment.
The error handler in makelevel swallows all write errors silently: @[last pair;txt;{x}] catches any signal and returns the error string unnoticed. A failed write to a closed handle or a function sink that signals will be silently discarded with no indication of failure. Use a handler that at minimum writes to stderr, e.g. {-2 "log sink error: ",x}.
| part:parts[1+i]; | ||
| code:first part; | ||
| rest:1_part; | ||
| if[not code in key subs;'`$"unsupported format char: ",enlist code]; |
There was a problem hiding this comment.
fmtmsg signals `type for a list message whose first element is not a 10h/string argument type (e.g. a symbol format string like (`fmt;arg) where fmt is a symbol), but only after partially processing. More critically, the args indexing args[i] will silently return null when i is out of range (more %s/%r specifiers in the format string than supplied arguments), producing malformed log lines with no error. There is no guard that count args matches the number of format specifiers.
| fatal:{[ctx;msg] logline["FATAL";ctx;msg];}; | ||
|
|
||
| / pre-wrapped log dependency dict ready to pass straight into any di.* module's init | ||
| / e.g. email.init[mylog.defaultdict] |
There was a problem hiding this comment.
add accepts either a bare handle or a 2-element (handle;fn) pair, but the remove function (line 151) only matches on first p (the handle component). If the user registers the same handle twice with different sender functions, remove will remove both entries for that handle rather than just the one for the specified sender. The intent of storing (handle;sender) pairs is to allow the same handle with different senders, but remove conflates them. The fix is to match on the full pair: not h~p when h is a pair, or keep the existing handle-only match only when h is a scalar.
| }[id;gv;lvl;] | ||
| }; | ||
|
|
||
| / update field k with value v in instance id's state |
There was a problem hiding this comment.
updInst writes back via .z.m.inst:state, but in add (line 112) the same assignment is done inline as .z.m.inst:state — bypassing updInst. This is inconsistent but not itself a bug. The real issue is that neither path is protected against concurrent modification: if two log instances call add/remove or setlvl concurrently (e.g. from a timer and a message handler in the same process), the read-modify-write on .z.m.inst is a race condition. This is a structural issue with the shared mutable dictionary design.
DIReview Summary1 critical | 4 warning(s) | 0 suggestion(s)
|
Log
log.qis the default logging implementation fordi.*modules. It writes formatted lines to stdout and satisfies the log dependency contract expected by modules such asdi.email.Usage
Output format:
Injecting into other modules
All
di.*modules that accept a log dependency expect a dictionary with keys`info`warn`error, each a function with signature{[ctx;msg]}.You can extend the injected dictionary with
trace,debug, andfatalfor modules that support them:createLog
createLogis a factory that returns an independent logger instance with level filtering, multiple output sinks, and configurable format templates. Each call tocreateLogproduces a separate instance with its own state.Sinks
A sink is a handle (integer file descriptor or function) passed to
add. If a function is provided it is called with the formatted line string. Built-in handles follow standard q conventions:1iis stdout,2iis stderr.Format templates
Three built-in formats are available:
basic(default)$p $l PID[$i] HOST[$h] $m2026-04-09T12:00:00.000000000 INFO PID[1234] HOST[myhost] messagesyslog<$s> $m<6> messageraw$mmessageTemplate variables:
$ptimestamp,$llevel,$iPID,$hhostname,$mmessage,$ssyslog severity number.API
traceParameters:
[ctx; msg]Write a trace-level message to stdout.
ctx— symbol context tag (e.g.`mymodule)msg— string messagedebugParameters:
[ctx; msg]Write a debug-level message to stdout.
infoParameters:
[ctx; msg]Write an info-level message to stdout.
warnParameters:
[ctx; msg]Write a warning-level message to stdout.
errorParameters:
[ctx; msg]Write an error-level message to stdout.
fatalParameters:
[ctx; msg]Write a fatal-level message to stdout.
createLogParameters: none
Returns an independent logger instance as a dictionary of functions. Each call returns a new instance with isolated state.
Returned keys:
`trace`debug`info`warn`error`fatal`add`remove`setfmt`getfmt`addfmt`setlvl`getlvltrace..fatal[msg]setlvl[lvl]`trace`debug`info`warn`error`fatalgetlvl[_]setfmt[name]getfmt[_]addfmt[name;template]add[handle;lvls]remove[handle;lvl]Log dependency contract
The log dependency contract used across
di.*modules requires a dictionary:di.logsatisfies this contract. You can also supply any custom implementation with the same signatures.