Docs/Data & text

Logging

Write log messages with levels.

On this page

valk.log writes log records with a level, a message and fields. Records go to standard error as one line each, as readable text or as JSON.

Write a record#

Valk
use valk.log

fn main() {
    log.info("server started", .{ "port" => 8080, "host" => "example.com" })
    // 2026-03-30T07:00:00.250Z INFO  server started port=8080 host=example.com
    log.warn("disk almost full")
    log.debug("not written")
}

The levels are debug, info, warn and error, each with a function of that name. Fields are a map of text, numbers, bools or null, and are optional. Records below the current level, info at the start, are dropped.

Settings#

Valk
use valk.log

fn main() {
    log.set_level(log.Level.debug)
    log.set_format(log.Format.json)
    log.debug("cache miss", .{ "key" => "user:17" })
    // {"time":"...","level":"debug","msg":"cache miss","key":"user:17"}

    let level = log.parse_level("warn") !? log.Level.info
    log.set_level(level)
}
Function Effect
set_level(level) Lowest level that is written
set_format(format) Format.text or Format.json (fields keep their type)
set_local_time(true) Timestamps in the local time zone instead of UTC
to_file(path) Append to a file, created when missing
to_stdout(), to_stderr() Write to standard output or standard error
parse_level(name) Read a level name, for example from an environment variable
enabled(level) Whether a level is written, to skip preparing expensive fields

Settings apply to every thread, and records from different threads never mix.

Add context to every record#

Valk
use valk.log

fn main() {
    let request = log.with(.{ "request" => "r-17" })
    request.info("query done", .{ "ms" => 12 })
    // 2026-03-30T07:00:00.250Z INFO  query done request=r-17 ms=12
}

with returns a logger that adds its fields to each record it writes. Calling with on that logger adds more.

Your own logger#

Valk
use valk.log

fn main() {
    let out = ByteBuffer.new(256)
    let logger = log.Logger.new(out, log.Level.debug, log.Format.text)
    logger.debug("step", .{ "n" => 1 })
    print(out.to_string()) // 2026-03-30T07:00:00.250Z DEBUG step n=1
}

Logger.new writes to any io.Writer, such as a file stream or a buffer, with its own level and format. Its level, format and local_time fields can be changed later.