Skip to content

Instantly share code, notes, and snippets.

@leecannon
Last active December 14, 2023 07:29
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save leecannon/d6f5d7e5af5881c466161270347ce84d to your computer and use it in GitHub Desktop.
Save leecannon/d6f5d7e5af5881c466161270347ce84d to your computer and use it in GitHub Desktop.
Quick overview of Zig's `std.log`

Out of date

This is an updated fork of this gist.

A simple overview of Zig's std.log

Logging functionality that supports:

  • If a log message should be printed is determined at comptime, meaning zero overhead for unprinted messages (so just leave the code peppered with debug logs, but when it makes sense scope them; so downstream users can filter them out)
  • Scoped log messages
  • Different log levels per scope
  • Overrideable log output (write to file, database, etc.)
  • All the standard std.fmt formatting magic

Basic Usage:

Just call std.log.X where X is the desired log level, for example:

std.log.info("This is an info message", .{});
std.log.crit("This is an crit message - {}", .{42});

Output:

info: This is an info message
critical: This is an crit message - 42

Log Levels:

The log level of a message determines if it will be printed.

Only if the level of the message is of an equal or greater severity than the global log level will it be printed (there is also overriding of the log level per scope to take into account, see below).

The log levels are, in order of severity:

  • emerg - Emergency: a condition that cannot be handled, usually followed by a panic.
  • alert - Alert: a condition that should be corrected immediately (e.g. database corruption)
  • crit - Critical: A bug has been detected or something has gone wrong and it will have an effect on the operation of the program.
  • err - Error: A bug has been detected or something has gone wrong but it is recoverable.
  • warn - Warning: it is uncertain if something has gone wrong or not, but the circumstances would be worth investigating.
  • notice- Notice: non-error but significant conditions.
  • info - Informational: general messages about the state of the program.
  • debug - Debug: messages only useful for debugging.

But what is the global log level?

The default log level is determined by the build mode like this:

/// The default log level is based on build mode.
pub const default_level: Level = switch (builtin.mode) {
    .Debug => .debug,
    .ReleaseSafe => .notice,
    .ReleaseFast => .err,
    .ReleaseSmall => .err,
};

Overriding this is easy just define a public log_level decl in the root file (where ever main is), like this:

pub const log_level: std.log.Level = .debug;

Scopes

All log messages include a scope.

When using the simple std.log.info and friends the scope default is used implicitly, the default log output function does not print the scope for messages with default as the scope.

In order to produce log messages with a different scope a scoped log needs to be created:

const my_log = std.log.scoped(.my_scope);

my_log.info("Hello from my_scope", .{});
std.log.info("Hello from default scope", .{});

Output:

info(my_scope): Hello from my_scope
info: Hello from default scope

Different log levels per scope

When a system gets large it naturally breaks down into sub-systems, it makes sense to use different log scopes per sub-system.

When debuging one of these sub-systems it makes sense to output a lot of debug log messages, however setting the global log level to debug will result in debug logs (and all other levels) from every log scope.

To resolve this std.log supports overriding the global log level on a per scope basis, in order to do so define a public scope_levels decl in the root file (where ever main is), like this:

pub const scope_levels = [_]std.log.ScopeLevel{
    .{ .scope = .library_a, .level = .debug },
    .{ .scope = .library_b, .level = .crit },
};

This will print all log messages from scope library_a, crit and higher from library_b and all other scopes will be printed based on the global log level as normal.

Overriding the log output

Overriding the log output allows great flexibility, libraries you use can just blindly log and the application decides where these message go; whether that is to stdout/stderr, file, database, socket, serial port, etc.

To to do so define a public log function in the root file (where ever main is), like this:

pub fn log(
    comptime message_level: std.log.Level,
    comptime scope: @Type(.EnumLiteral),
    comptime format: []const u8,
    args: anytype,
) void {
    // Implementation here   
}

Checking the global log level and per scope log level is already done for you, meaning this function will only be called when there is a log message that should be output. ~~

@gagbo
Copy link

gagbo commented Sep 10, 2023

Hello, I tried to set the log levels per scope but failed.

  1. it seems that the name of the variable to override is log_scope_levels and not scope_levels: https://github.com/ziglang/zig/blob/d2014fe9713794f6cc0830301a1110d5e92d0ff0/lib/std/std.zig#L239

  2. Trying any of the 2 constants in my root file (src/main.zig) failed to filter the logs as I wanted

@mvolkmann
Copy link

@gagbo I'm hitting the same issue as you. All the logging levels are enabled by default. It's not obvious how to disable some of them.

@mvolkmann
Copy link

@gagbo I just figured out how to set a log level that is not specific to a scope, but is global. Maybe you already know how to do this. I added this at the top scope:

pub const std_options = struct {
    // Set this to .info, .debug, .warn, or .err.
    pub const log_level = .warn;
};

With this setting, output from std.log.info and std-log-debug is filtered out.

@leecannon
Copy link
Author

I recommend this updated gist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment