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
Just call std.log.X
where X is the desired log level, for example:
std.log.info("This is an info message", .{});
std.log.err("This is an err message - {}", .{42});
Output:
info: This is an info message
err: This is an err message - 42
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:
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.info
- Informational: general messages about the state of the program.debug
- Debug: messages only useful for debugging.
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 => .info,
.ReleaseFast, .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 std_options = .{
// Set the log level to info
.log_level = .info,
};
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
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 const std_options = .{
.logFn = myLogFn,
};
pub fn myLogFn(
comptime level: std.log.Level,
comptime scope: @TypeOf(.EnumLiteral),
comptime format: []const u8,
args: anytype,
) void {
// Implementation
}
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.
- std-doc: log
- Thanks @johnbcodes: comment
- Thanks @david-haerer: comment
Thanks for updating the original!
scope_levels
has been changed tolog_scope_levels