std.log: simplify to 4 distinct log levels

Over the last year of using std.log in practice, it has become clear to
me that having the current 8 distinct log levels does more harm than
good. It is too subjective which level a given message should have which
makes filtering based on log level weaker as not all messages will have
been assigned the log level one might expect.

Instead, more granular filtering should be achieved by leveraging the
logging scope feature. Filtering based on a combination of scope and log
level should be sufficiently powerful for all use-cases.

Note that the self hosted compiler has already limited itself to 4
distinct log levels for many months and implemented granular filtering
based on both log scope and level. This has worked very well in practice
while working on the self hosted compiler.
This commit is contained in:
Isaac Freund 2021-10-24 13:13:06 +02:00 committed by Andrew Kelley
parent 6cf5305e47
commit f7b090d707
5 changed files with 59 additions and 138 deletions

View File

@ -40,12 +40,8 @@ pub fn ScopedLoggingAllocator(
// This function is required as the `std.log.log` function is not public // This function is required as the `std.log.log` function is not public
inline fn logHelper(comptime log_level: std.log.Level, comptime format: []const u8, args: anytype) void { inline fn logHelper(comptime log_level: std.log.Level, comptime format: []const u8, args: anytype) void {
switch (log_level) { switch (log_level) {
.emerg => log.emerg(format, args),
.alert => log.alert(format, args),
.crit => log.crit(format, args),
.err => log.err(format, args), .err => log.err(format, args),
.warn => log.warn(format, args), .warn => log.warn(format, args),
.notice => log.notice(format, args),
.info => log.info(format, args), .info => log.info(format, args),
.debug => log.debug(format, args), .debug => log.debug(format, args),
} }
@ -120,6 +116,6 @@ pub fn ScopedLoggingAllocator(
/// This allocator is used in front of another allocator and logs to `std.log` /// This allocator is used in front of another allocator and logs to `std.log`
/// on every call to the allocator. /// on every call to the allocator.
/// For logging to a `std.io.Writer` see `std.heap.LogToWriterAllocator` /// For logging to a `std.io.Writer` see `std.heap.LogToWriterAllocator`
pub fn loggingAllocator(parent_allocator: *Allocator) LoggingAllocator(.debug, .crit) { pub fn loggingAllocator(parent_allocator: *Allocator) LoggingAllocator(.debug, .err) {
return LoggingAllocator(.debug, .crit).init(parent_allocator); return LoggingAllocator(.debug, .err).init(parent_allocator);
} }

View File

@ -18,8 +18,8 @@
//! ``` //! ```
//! const std = @import("std"); //! const std = @import("std");
//! //!
//! // Set the log level to warning //! // Set the log level to info
//! pub const log_level: std.log.Level = .warn; //! pub const log_level: std.log.Level = .info;
//! //!
//! // Define root.log to override the std implementation //! // Define root.log to override the std implementation
//! pub fn log( //! pub fn log(
@ -28,17 +28,17 @@
//! comptime format: []const u8, //! comptime format: []const u8,
//! args: anytype, //! args: anytype,
//! ) void { //! ) void {
//! // Ignore all non-critical logging from sources other than //! // Ignore all non-error logging from sources other than
//! // .my_project, .nice_library and .default //! // .my_project, .nice_library and .default
//! const scope_prefix = "(" ++ switch (scope) { //! const scope_prefix = "(" ++ switch (scope) {
//! .my_project, .nice_library, .default => @tagName(scope), //! .my_project, .nice_library, .default => @tagName(scope),
//! else => if (@enumToInt(level) <= @enumToInt(std.log.Level.crit)) //! else => if (@enumToInt(level) <= @enumToInt(std.log.Level.err))
//! @tagName(scope) //! @tagName(scope)
//! else //! else
//! return, //! return,
//! } ++ "): "; //! } ++ "): ";
//! //!
//! const prefix = "[" ++ @tagName(level) ++ "] " ++ scope_prefix; //! const prefix = "[" ++ level.asText() ++ "] " ++ scope_prefix;
//! //!
//! // Print the message to stderr, silently ignoring any errors //! // Print the message to stderr, silently ignoring any errors
//! const held = std.debug.getStderrMutex().acquire(); //! const held = std.debug.getStderrMutex().acquire();
@ -49,23 +49,23 @@
//! //!
//! pub fn main() void { //! pub fn main() void {
//! // Using the default scope: //! // Using the default scope:
//! std.log.info("Just a simple informational log message", .{}); // Won't be printed as log_level is .warn //! std.log.debug("A borderline useless debug log message", .{}); // Won't be printed as log_level is .info
//! std.log.warn("Flux capacitor is starting to overheat", .{}); //! std.log.info("Flux capacitor is starting to overheat", .{});
//! //!
//! // Using scoped logging: //! // Using scoped logging:
//! const my_project_log = std.log.scoped(.my_project); //! const my_project_log = std.log.scoped(.my_project);
//! const nice_library_log = std.log.scoped(.nice_library); //! const nice_library_log = std.log.scoped(.nice_library);
//! const verbose_lib_log = std.log.scoped(.verbose_lib); //! const verbose_lib_log = std.log.scoped(.verbose_lib);
//! //!
//! my_project_log.info("Starting up", .{}); // Won't be printed as log_level is .warn //! my_project_log.debug("Starting up", .{}); // Won't be printed as log_level is .info
//! nice_library_log.err("Something went very wrong, sorry", .{}); //! nice_library_log.warn("Something went very wrong, sorry", .{});
//! verbose_lib_log.err("Added 1 + 1: {}", .{1 + 1}); // Won't be printed as it gets filtered out by our log function //! verbose_lib_log.warn("Added 1 + 1: {}", .{1 + 1}); // Won't be printed as it gets filtered out by our log function
//! } //! }
//! ``` //! ```
//! Which produces the following output: //! Which produces the following output:
//! ``` //! ```
//! [warn] (default): Flux capacitor is starting to overheat //! [info] (default): Flux capacitor is starting to overheat
//! [err] (nice_library): Something went very wrong, sorry //! [warning] (nice_library): Something went very wrong, sorry
//! ``` //! ```
const std = @import("std.zig"); const std = @import("std.zig");
@ -73,42 +73,29 @@ const builtin = @import("builtin");
const root = @import("root"); const root = @import("root");
pub const Level = enum { pub const Level = enum {
/// Emergency: a condition that cannot be handled, usually followed by a /// Error: something has gone wrong. This might be recoverable or might
/// panic. /// be followed by the program exiting.
emerg,
/// Alert: a condition that should be corrected immediately (e.g. database
/// corruption).
alert,
/// Critical: A bug has been detected or something has gone wrong and it
/// will have an effect on the operation of the program.
crit,
/// Error: A bug has been detected or something has gone wrong but it is
/// recoverable.
err, err,
/// Warning: it is uncertain if something has gone wrong or not, but the /// Warning: it is uncertain if something has gone wrong or not, but the
/// circumstances would be worth investigating. /// circumstances would be worth investigating.
warn, warn,
/// Notice: non-error but significant conditions. /// Info: general messages about the state of the program.
notice,
/// Informational: general messages about the state of the program.
info, info,
/// Debug: messages only useful for debugging. /// Debug: messages only useful for debugging.
debug, debug,
/// Returns a string literal of the given level in full text form. /// Returns a string literal of the given level in full text form.
pub fn asText(comptime self: Level) switch (self) { pub fn asText(comptime self: Level) switch (self) {
.emerg => @TypeOf("emergency"),
.crit => @TypeOf("critical"),
.err => @TypeOf("error"), .err => @TypeOf("error"),
.warn => @TypeOf("warning"), .warn => @TypeOf("warning"),
else => @TypeOf(@tagName(self)), .info => @TypeOf("info"),
.debug => @TypeOf("debug"),
} { } {
return switch (self) { return switch (self) {
.emerg => "emergency",
.crit => "critical",
.err => "error", .err => "error",
.warn => "warning", .warn => "warning",
else => @tagName(self), .info => "info",
.debug => "debug",
}; };
} }
}; };
@ -116,9 +103,8 @@ pub const Level = enum {
/// The default log level is based on build mode. /// The default log level is based on build mode.
pub const default_level: Level = switch (builtin.mode) { pub const default_level: Level = switch (builtin.mode) {
.Debug => .debug, .Debug => .debug,
.ReleaseSafe => .notice, .ReleaseSafe => .info,
.ReleaseFast => .err, .ReleaseFast, .ReleaseSmall => .err,
.ReleaseSmall => .err,
}; };
/// The current log level. This is set to root.log_level if present, otherwise /// The current log level. This is set to root.log_level if present, otherwise
@ -188,39 +174,18 @@ pub fn defaultLog(
/// provided here. /// provided here.
pub fn scoped(comptime scope: @Type(.EnumLiteral)) type { pub fn scoped(comptime scope: @Type(.EnumLiteral)) type {
return struct { return struct {
/// Log an emergency message. This log level is intended to be used /// Deprecated. TODO: replace with @compileError() after 0.9.0 is released
/// for conditions that cannot be handled and is usually followed by a panic. pub const emerg = @This().err;
pub fn emerg(
comptime format: []const u8,
args: anytype,
) void {
@setCold(true);
log(.emerg, scope, format, args);
}
/// Log an alert message. This log level is intended to be used for /// Deprecated. TODO: replace with @compileError() after 0.9.0 is released
/// conditions that should be corrected immediately (e.g. database corruption). pub const alert = @This().err;
pub fn alert(
comptime format: []const u8,
args: anytype,
) void {
@setCold(true);
log(.alert, scope, format, args);
}
/// Log a critical message. This log level is intended to be used /// Deprecated. TODO: replace with @compileError() after 0.9.0 is released
/// when a bug has been detected or something has gone wrong and it will have pub const crit = @This().err;
/// an effect on the operation of the program.
pub fn crit(
comptime format: []const u8,
args: anytype,
) void {
@setCold(true);
log(.crit, scope, format, args);
}
/// Log an error message. This log level is intended to be used when /// Log an error message. This log level is intended to be used
/// a bug has been detected or something has gone wrong but it is recoverable. /// when something has gone wrong. This might be recoverable or might
/// be followed by the program exiting.
pub fn err( pub fn err(
comptime format: []const u8, comptime format: []const u8,
args: anytype, args: anytype,
@ -239,14 +204,8 @@ pub fn scoped(comptime scope: @Type(.EnumLiteral)) type {
log(.warn, scope, format, args); log(.warn, scope, format, args);
} }
/// Log a notice message. This log level is intended to be used for /// Deprecated. TODO: replace with @compileError() after 0.9.0 is released
/// non-error but significant conditions. pub const notice = @This().info;
pub fn notice(
comptime format: []const u8,
args: anytype,
) void {
log(.notice, scope, format, args);
}
/// Log an info message. This log level is intended to be used for /// Log an info message. This log level is intended to be used for
/// general messages about the state of the program. /// general messages about the state of the program.
@ -271,24 +230,18 @@ pub fn scoped(comptime scope: @Type(.EnumLiteral)) type {
/// The default scoped logging namespace. /// The default scoped logging namespace.
pub const default = scoped(.default); pub const default = scoped(.default);
/// Log an emergency message using the default scope. This log level is /// Deprecated. TODO: replace with @compileError() after 0.9.0 is released
/// intended to be used for conditions that cannot be handled and is usually pub const emerg = default.err;
/// followed by a panic.
pub const emerg = default.emerg;
/// Log an alert message using the default scope. This log level is intended to /// Deprecated. TODO: replace with @compileError() after 0.9.0 is released
/// be used for conditions that should be corrected immediately (e.g. database pub const alert = default.err;
/// corruption).
pub const alert = default.alert;
/// Log a critical message using the default scope. This log level is intended /// Deprecated. TODO: replace with @compileError() after 0.9.0 is released
/// to be used when a bug has been detected or something has gone wrong and it pub const crit = default.err;
/// will have an effect on the operation of the program.
pub const crit = default.crit;
/// Log an error message using the default scope. This log level is intended to /// Log an error message using the default scope. This log level is intended to
/// be used when a bug has been detected or something has gone wrong but it is /// be used when something has gone wrong. This might be recoverable or might
/// recoverable. /// be followed by the program exiting.
pub const err = default.err; pub const err = default.err;
/// Log a warning message using the default scope. This log level is intended /// Log a warning message using the default scope. This log level is intended
@ -296,9 +249,8 @@ pub const err = default.err;
/// the circumstances would be worth investigating. /// the circumstances would be worth investigating.
pub const warn = default.warn; pub const warn = default.warn;
/// Log a notice message using the default scope. This log level is intended to /// Deprecated. TODO: replace with @compileError() after 0.9.0 is released
/// be used for non-error but significant conditions. pub const notice = default.info;
pub const notice = default.notice;
/// Log an info message using the default scope. This log level is intended to /// Log an info message using the default scope. This log level is intended to
/// be used for general messages about the state of the program. /// be used for general messages about the state of the program.

View File

@ -4398,7 +4398,7 @@ test "zig fmt: regression test for #5722" {
\\ while (it.next()) |node| \\ while (it.next()) |node|
\\ view_tags.append(node.view.current_tags) catch { \\ view_tags.append(node.view.current_tags) catch {
\\ c.wl_resource_post_no_memory(self.wl_resource); \\ c.wl_resource_post_no_memory(self.wl_resource);
\\ log.crit(.river_status, "out of memory", .{}); \\ log.err(.river_status, "out of memory", .{});
\\ return; \\ return;
\\ }; \\ };
\\} \\}

View File

@ -27,7 +27,7 @@ const crash_report = @import("crash_report.zig");
pub usingnamespace crash_report.root_decls; pub usingnamespace crash_report.root_decls;
pub fn fatal(comptime format: []const u8, args: anytype) noreturn { pub fn fatal(comptime format: []const u8, args: anytype) noreturn {
std.log.emerg(format, args); std.log.err(format, args);
process.exit(1); process.exit(1);
} }
@ -94,7 +94,7 @@ const usage = if (debug_extensions_enabled) debug_usage else normal_usage;
pub const log_level: std.log.Level = switch (builtin.mode) { pub const log_level: std.log.Level = switch (builtin.mode) {
.Debug => .debug, .Debug => .debug,
.ReleaseSafe, .ReleaseFast => .info, .ReleaseSafe, .ReleaseFast => .info,
.ReleaseSmall => .crit, .ReleaseSmall => .err,
}; };
var log_scopes: std.ArrayListUnmanaged([]const u8) = .{}; var log_scopes: std.ArrayListUnmanaged([]const u8) = .{};
@ -120,14 +120,7 @@ pub fn log(
} else return; } else return;
} }
// We only recognize 4 log levels in this application. const prefix1 = comptime level.asText();
const level_txt = switch (level) {
.emerg, .alert, .crit, .err => "error",
.warn => "warning",
.notice, .info => "info",
.debug => "debug",
};
const prefix1 = level_txt;
const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
// Print the message to stderr, silently ignoring any errors // Print the message to stderr, silently ignoring any errors

View File

@ -435,8 +435,8 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
\\pub const log_level: std.log.Level = .debug; \\pub const log_level: std.log.Level = .debug;
\\ \\
\\pub const scope_levels = [_]std.log.ScopeLevel{ \\pub const scope_levels = [_]std.log.ScopeLevel{
\\ .{ .scope = .a, .level = .alert }, \\ .{ .scope = .a, .level = .warn },
\\ .{ .scope = .c, .level = .emerg }, \\ .{ .scope = .c, .level = .err },
\\}; \\};
\\ \\
\\const loga = std.log.scoped(.a); \\const loga = std.log.scoped(.a);
@ -452,10 +452,6 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
\\ logb.info("", .{}); \\ logb.info("", .{});
\\ logc.info("", .{}); \\ logc.info("", .{});
\\ \\
\\ loga.notice("", .{});
\\ logb.notice("", .{});
\\ logc.notice("", .{});
\\
\\ loga.warn("", .{}); \\ loga.warn("", .{});
\\ logb.warn("", .{}); \\ logb.warn("", .{});
\\ logc.warn("", .{}); \\ logc.warn("", .{});
@ -463,18 +459,6 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
\\ loga.err("", .{}); \\ loga.err("", .{});
\\ logb.err("", .{}); \\ logb.err("", .{});
\\ logc.err("", .{}); \\ logc.err("", .{});
\\
\\ loga.crit("", .{});
\\ logb.crit("", .{});
\\ logc.crit("", .{});
\\
\\ loga.alert("", .{});
\\ logb.alert("", .{});
\\ logc.alert("", .{});
\\
\\ loga.emerg("", .{});
\\ logb.emerg("", .{});
\\ logc.emerg("", .{});
\\} \\}
\\pub fn log( \\pub fn log(
\\ comptime level: std.log.Level, \\ comptime level: std.log.Level,
@ -483,22 +467,18 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
\\ args: anytype, \\ args: anytype,
\\) void { \\) void {
\\ const level_txt = comptime level.asText(); \\ const level_txt = comptime level.asText();
\\ const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; \\ const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "):";
\\ const stdout = std.io.getStdOut().writer(); \\ const stdout = std.io.getStdOut().writer();
\\ nosuspend stdout.print(level_txt ++ prefix2 ++ format ++ "\n", args) catch return; \\ nosuspend stdout.print(level_txt ++ prefix2 ++ format ++ "\n", args) catch return;
\\} \\}
, ,
\\debug(b): \\debug(b):
\\info(b): \\info(b):
\\notice(b): \\warning(a):
\\warning(b): \\warning(b):
\\error(b): \\error(a):
\\critical(b): \\error(b):
\\alert(a): \\error(c):
\\alert(b):
\\emergency(a):
\\emergency(b):
\\emergency(c):
\\ \\
); );
@ -534,7 +514,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
, ,
\\debug: alloc - success - len: 10, ptr_align: 1, len_align: 0 \\debug: alloc - success - len: 10, ptr_align: 1, len_align: 0
\\debug: shrink - success - 10 to 5, len_align: 0, buf_align: 1 \\debug: shrink - success - 10 to 5, len_align: 0, buf_align: 1
\\critical: expand - failure: OutOfMemory - 5 to 20, len_align: 0, buf_align: 1 \\error: expand - failure: OutOfMemory - 5 to 20, len_align: 0, buf_align: 1
\\debug: free - success - len: 5 \\debug: free - success - len: 5
\\ \\
); );