Logging
Logging API and performance considerations
GreyCat allows you to log messages at different levels of information. Unlike other programming languages, this native construction is compiled to face big data requirements. For instance, there is no penalty to using log trace extensively during dev or debug phases. In production, GreyCat simply removes all overhead, even the log string line construction.
The following functions allow you to log a message, at the desired granularity.
- error(v: any?) outputs
vat the error level. - warn(v: any?) outputs
vat the warn level. - info(v: any?) outputs
vat the info level. - trace(v: any?) outputs
vat the trace level.
fn main() {
var k = -12;
trace("k == ${k}");
if (k < 0) {
info("${k} negative");
return;
}
info("done");
}
Runtime usage
The log level is chosen when the program starts, with the --log option: greycat run --log=trace or greycat serve --log=warn.
The levels are none, error, warn, info and trace.
It is also possible to set the log level as environment variable (.env file) within the project root: GREYCAT_LOG=info.
By default, GreyCat logs messages at the info level: error(), warn(), info(), the sample above prints the following:
INFO 2026-10-27T09:28:02.829422Z 1/7 project::main -12 negative
With --log=trace, the trace() message is printed as well:
TRACE 2026-10-27T09:28:02.829396Z 1/7 project::main k == -12
INFO 2026-10-27T09:28:02.829422Z 1/7 project::main -12 negative
Each line shows the level, the time, the user id and task id of the caller, and the function that logged the message.
Where logs go
Every log record that passes the level check is appended to the log stream of the root user, the file files/root/streams/log.ndjson, one JSON object per line.
That file is written by every command that opens the store (run, serve, test, …), and it is never rotated or truncated.
{"level":"trace","time":"2026-10-27T09:28:02.829396Z","user_id":1,"task_id":7,"job_id":null,"src":"project::main","data":"k == -12"}
{"level":"info","time":"2026-10-27T09:28:02.829422Z","user_id":1,"task_id":7,"job_id":null,"src":"project::main","data":"-12 negative"}
The terminal only receives a copy of the records when it is a TTY, and the JSON form of the startup lines printed before the store is open. A server whose output is redirected (systemd, docker, a file) prints those startup lines and then goes quiet, so read the stream instead:
tail -f files/root/streams/log.ndjson
jq -c 'select(.level == "warn" or .level == "error")' files/root/streams/log.ndjson
Records are buffered and written in batches, once --log_flush_size bytes are pending (64KB by default) and at the latest --log_flush_step after the first of them (1s by default).
--log_flush_on_error writes the buffer as soon as an error is logged.
From GCL, the root user reads the stream back as Log records:
fn last_errors(): Array<Log> {
var errors = Array<Log> {};
var reader = Stream<Log>::get("log").reader(0);
while (reader.can_read()) {
var record = reader.read();
if (record.level == LogLevel::error) {
errors.add(record);
}
}
return errors;
}
Runtime metrics (task durations, host usage, HTTP requests) are not log records and are not controlled by --log. See Metrics.