The kdb-x logging module is a simple logging framework.
For the purpose of documenting, the coded examples provided here assume the logging library has been loaded into the namespace .logger, and a logging instance has been created under the .log namespace like so:
.logger:use`kx.log
.log:.logger.createLog[]But remember, under the kdb-x module framework this can be loaded into a project under any name desired.
The logging module supports multiple independent log instances within a single q process. This is achieved using a factory design pattern: the module exposes a single function, createLog, which returns a fully initialized, independent, logger instance.
.logger.createLog[]This returns a dictionary containing all logging APIs, state and accessors so multiple loggers can operate independently.
This is useful when a single q process has multiple sections, and a particular section needs to be debugged. E.g.
q).logger:use`kx.log;
q)\d .foo
q.foo)logger:.logger.createLog[];
q.foo)f:{logger.trace "trace from foo"};
q.foo)\d .bar
q.bar)logger:.logger.createLog[];
q.bar)f:{logger.trace "trace from bar";logger.warn "warn from bar"};
q.bar)\d .
q).foo.f[]; // expect no output since default log level is info
q).bar.f[]; // expect output since there is a warn log
2025.10.23D11:09:47.645765308 warn PID[94486] HOST[a-lptp-p2myzzg4] warn from bar
q).foo.logger.setlvl `trace; // change foo log level to trace
q).foo.f[]; // shows trace
2025.10.23D11:10:18.631553544 trace PID[94486] HOST[a-lptp-p2myzzg4] trace from foo
q).bar.f[]; // still only shows warn since log level wasn't chanted for bar
2025.10.23D11:10:34.057420473 warn PID[94486] HOST[a-lptp-p2myzzg4] warn from barTo log to a particular level, run with .log.xxxx, where xxxx is one of:
tracedebuginfowarnerrorfatal
These can each be called in the following ways
Note: Referencing
infoin the following examples but this extends to each of the above APIs
.log.info[message]Where message is either
- A string
- A general list (see variable logging)
q).log.info "Hello World!"
2025.10.13D12:37:12.911621911 info PID[658720] HOST[a-lptp-p2myzzg4] Hello World!Variables can be included in log messages using arguments in junction with format specifiers. The formatting here utilises KX's printf module.
.log.info (message;arg1;arg2;...)Where arg1,arg2,... are the variables to be formatted in the message. The number of arguments depends on the number of format specifiers are in the message string.
q).log.info ("myvar: %r";`foo)
2025.10.30D10:22:19.124336219 info PID[1937319] HOST[a-lptp-p2myzzg4] myvar: `foo
q).log.info ("var1: %s, var2: %r";`foo;123)
2025.10.30D10:22:38.850194514 info PID[1937319] HOST[a-lptp-p2myzzg4] var1: foo, var2: 123
q).log.info ("var1: %r, var2: %s";"\t tab";"\t tab")
2025.10.30D10:22:52.071370289 info PID[1937319] HOST[a-lptp-p2myzzg4] var1: "\t tab", var2: tab
q).log.info ("formatted float: %08.3f";3.1415926536)
2025.10.30D10:23:34.987634986 info PID[1937319] HOST[a-lptp-p2myzzg4] formatted float: 3.142Log levels specify at which severity to report.
Get the current log level
.log.getlvl[]Returns the current log level (symbol)
q).log.getlvl[]
`infoSet the log level
.log.setlvl[level]Where level is an available log level
q).log.info "hi"
2025.10.13D11:39:51.014213515 info PID[489737] HOST[a-lptp-p2myzzg4] hi
q).log.setlvl `error
q).log.info "you can't see me"Formats dictate how a log message is formatted.
The following are the replacement rules for a formatter:
| Pattern | Replacement | Notes |
|---|---|---|
| $a | .z.a | IP address |
| $c | .z.c | cores |
| $f | .z.f | file |
| $h | .z.h | host |
| $i | .z.i | PID |
| $K | .z.K | version |
| $k | .z.k | release date |
| $o | .z.o | OS version |
| $u | .z.u | user ID |
| $d | .z.d | UTC date |
| $D | .z.D | local date |
| $n | .z.n | UTC timespan |
| $N | .z.N | local timespan |
| $p | .z.p | UTC timestamp |
| $P | .z.P | local timestamp |
| $t | .z.t | UTC time |
| $T | .z.T | local time |
| $z | .z.z | UTC datetime |
| $Z | .z.Z | local datetime |
| $l | log level of this message | |
| $m | user provided log message | |
| $w | " " sv string system"w" | current memory usage |
| $s | syslog level of this message | converts `info to 6 for example |
This module provides off the shelf the following formats:
- basic
"$p $l PID[$i] HOST[$h] $m\r\n"- syslog
"<$s> $m\r\n"- raw
"$m\r\n"Tip: Use the raw format in junction with
.j.jto log jsonq).log.setfmt `raw q).log.info .j.j `foo`bar!til 2 {"foo":0,"bar":1}
List registered formats
.log.fmts[];Returns a dictionary, where the keys are the names of the registered format, and the values are the format rules.
q).log.fmts[]
basic | "$p $l PID[$i] HOST[$h] $m\r\n"
syslog| "<$s> $m\r\n"
raw | "$m\r\n"Set log format
.log.setfmt[name]Where name is a registered format, visible in .log.fmts[].
q).log.info "hi"
2025.10.13D11:16:34.445967945 info PID[408461] HOST[a-lptp-p2myzzg4] hi
q).log.setfmt `syslog
q).log.info "hi"
<6> hi
q).log.setfmt `raw
q).log.info .j.j `foo`bar!til 2
{"foo":0,"bar":1}Check current log format
.log.getfmt[]Returns the current format as a symbol
q).log.getfmt[]
`basicRegister custom format
.log.addfmt[name;format]Where
nameis a name to register the new format as (symbol)formatis a string defining the format of the output (string)
q).log.addfmt[`foo;"bar: $m\r\n"]
q).log.setfmt`foo
q).log.info "lorem"
bar: loremSinks are the destination for logs, can be qipc, file or system handles.
List the sinks
.log.sinks[]Returns a dictionary, with the keys of the available levels, and the values of the handles subscribed to that level.
q).log.sinks[]
trace| 1
debug| 1
info | 1
warn | 1
error| 2
fatal| 2Remove a handle from a level
.log.remove[handle;level]Where
handleis an integer,levelis a log level
removes the handle from the level. Returns the handle removed
q).log.sinks[]
trace| 1
debug| 1
info | 1
warn | 1
error| 2
fatal| 2
q).log.info "hi"
2025.10.13D11:55:51.028205975 info PID[521724] HOST[a-lptp-p2myzzg4] hi
q).log.remove[1;`info]
1
q).log.info "you can't see me"The add API has two different ways of being called
Add a file or system sink to a level
.log.add[handle;level]Where
handleis an integerlevelis a log level
adds the handle to the level. Returns the handle added
q).log.sinks[]
trace| 1
debug| 1
info | 1
warn | 1
error| 2
fatal| 2
q).log.error "hi" // logs to stderr system handle
2025.10.13D12:00:56.977883075 error PID[549311] HOST[a-lptp-p2myzzg4] hi
q).log.add[1;`error]
1
q).log.error "hi" // logs to both stderr and stdout system handles
2025.10.13D12:01:45.822436756 error PID[549311] HOST[a-lptp-p2myzzg4] hi
2025.10.13D12:01:45.822436756 error PID[549311] HOST[a-lptp-p2myzzg4] hiqIPC handles require additionally a function to call on the server side.
Add a qipc sink to a level
.log.add[(handle;function);level]Where
handleis an integerfunctionis a dyadic function that accepts arguments of [handle;message], where message is the formatted string representation of a log calllevelis a log level
adds the qIPC handle to the level. Returns the handle added and function.
q)\q -p 5000
q)system "sleep 1" // give the process time to start before connecting
q)h:hopen 5000
q).log.add[(h;{x@({-1 reverse x};y)});`info]
4i
{x@({-1 reverse x};y)}
q).log.info "hi"
2025.10.13D12:12:20.903165119 info PID[579844] HOST[a-lptp-p2myzzg4] hi
ih ]4gzzym2p-ptpl-a[TSOH ]448975[DIP ofni 911561309.02:21:21D31.01.5202When attempting to log to a non-existent handle, the handle is removed from all levels in sink and a warning message is displayed ("lost connection to handle %1, dropping";handle)
q).log.add[999i;`info] // handle 999i is not a valid handle
999i
q).log.info "hi"
2025.10.13D12:14:54.784304296 info PID[593137] HOST[a-lptp-p2myzzg4] hi
2025.10.13D12:14:54.784402446 warn PID[593137] HOST[a-lptp-p2myzzg4] lost connection to handle 999, droppingq).log.add[(999i;{x@({-1 reverse x};y)});`info] // handle 999i is not a valid qIPC handle
999i
{x@({-1 reverse x};y)}
q).log.info "hi"
2025.10.13D12:19:48.981780007 info PID[607636] HOST[a-lptp-p2myzzg4] hi
2025.10.13D12:19:48.981878933 warn PID[607636] HOST[a-lptp-p2myzzg4] lost connection to handle 999, dropping