Logging

Printing everything to the console is fine during development, but in a real application you usually want more control: write to a file, filter by severity, and keep the output even after the terminal session ends. Loft's logging functions give you that without changing your source code.

The four severity levels

Choose the level that matches how serious the event is:

Configuring the log destination

By default, log calls do nothing — no file is written, no console output is produced. To switch logging on, place a log.conf file in the same directory as your .loft file, or pass --log-conf path/to/log.conf on the command line. The directory is exact: a log.conf one level up is not found, and a program with no config logs nothing and says nothing.

To see every setting with its default, print a documented template: loft --generate-log-config # writes it to the terminal loft --generate-log-config log.conf # writes it to that file

A minimal log.conf looks like this:

[log]
file  = log.txt    # write messages here (relative to the .loft file)
level = info       # minimum level to record; choices: info warn error fatal
                   # (the default, when you leave this out, is 'warn')
[rotation]
max_size_mb = 500  # start a new log file after this many megabytes
daily       = true # also start a new file at midnight UTC
max_files   = 10   # keep at most this many log files (older ones are deleted)
[rate_limit]
per_site = 5       # suppress messages from the same source line after 5/minute
[levels]
# Override the global level for one file, by its name alone:
"debug_tool.loft" = info
"src/"            = error   # a path prefix, matched from the project root

Production mode

With production = true in the [log] section of log.conf, or --production on the command line:

The program keeps running and the problem is captured in the log — useful for long-running services where a single error should not bring everything down. It still exits non-zero at the end, so a supervisor watching the exit status learns that something went wrong.

Using format strings in log messages

Log messages are plain text, but you can embed any expression using the same {...} format syntax as everywhere else in Loft.

The message is built BEFORE the call decides whether to write it — the argument is an ordinary expression, evaluated like any other. So a message that is discarded still costs what it costs, and that is true whether it was filtered out by the level, suppressed by the rate limit, or dropped because there is no log.conf at all. Keep an expensive call out of a message on a hot path, or guard the whole log line with an if.

fn main() {

Without a log.conf these calls do nothing — the tests below pass even though no output is produced.

  log_info("starting up");
  log_warn("this is a warning");
  log_error("something went wrong");
  log_fatal("critical failure");

Example log.txt output

When a log.conf is present with level = info, the four calls above produce entries like this in log.txt:

2026-09-01T08:35:38.971Z INFO   app.loft:3  starting up
2026-09-01T08:35:38.971Z WARN   app.loft:4  this is a warning
2026-09-01T08:35:38.971Z ERROR  app.loft:5  something went wrong
2026-09-01T08:35:38.971Z FATAL  app.loft:6  critical failure

Each line contains: a UTC timestamp to the millisecond, the severity level, the source file and line number, then the message. This makes it easy to search the file for errors or trace back to the exact line that produced a message. The file path is written the way a [levels] key addresses it: relative to the project root inside a project, the bare file name for a script. A true assert never logs anything — it is only the false case that logs.

  assert(true, "this should never fail");

Typical usage pattern

In a real program you would write something like:

fn process(item: Item) {
    log_info("processing item {item.id}");
    result = do_work(item);
    if !result {
        log_error("work failed for item {item.id}");
        return;
    }
    log_info("finished item {item.id} successfully");
}

The log messages give you a record of exactly what the program did and where it went wrong, without cluttering the terminal during normal runs.

  println("logging test passed");
}