From 92151216882e2bfacbbb1b6cae3c5281bf9dd03c Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Tue, 28 Oct 2025 12:43:13 +0000 Subject: [PATCH] std.log: colorize output in default implementation Also remove the example implementation from the file doc comment; it's better to just link to `defaultLog` as an example, since this avoids writing the example implementation twice and prevents the example from bitrotting. --- lib/std/log.zig | 111 +++++++++++++++--------------------------------- 1 file changed, 34 insertions(+), 77 deletions(-) diff --git a/lib/std/log.zig b/lib/std/log.zig index 1b1c98dd43..51b01fe47f 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -13,63 +13,15 @@ //! `const log = std.log.scoped(.libfoo);` to use .libfoo as the scope of its //! log messages. //! -//! An example `logFn` might look something like this: -//! +//! For an example implementation of the `logFn` function, see `defaultLog`, +//! which is the default implementation. It outputs to stderr, using color if +//! the detected `std.Io.tty.Config` supports it. Its output looks like this: //! ``` -//! const std = @import("std"); -//! -//! pub const std_options: std.Options = .{ -//! // Set the log level to info -//! .log_level = .info, -//! -//! // Define logFn to override the std implementation -//! .logFn = myLogFn, -//! }; -//! -//! pub fn myLogFn( -//! comptime level: std.log.Level, -//! comptime scope: @Type(.enum_literal), -//! comptime format: []const u8, -//! args: anytype, -//! ) void { -//! // Ignore all non-error logging from sources other than -//! // .my_project, .nice_library and the default -//! const scope_prefix = "(" ++ switch (scope) { -//! .my_project, .nice_library, std.log.default_log_scope => @tagName(scope), -//! else => if (@intFromEnum(level) <= @intFromEnum(std.log.Level.err)) -//! @tagName(scope) -//! else -//! return, -//! } ++ "): "; -//! -//! const prefix = "[" ++ comptime level.asText() ++ "] " ++ scope_prefix; -//! -//! // Print the message to stderr, silently ignoring any errors -//! std.debug.lockStdErr(); -//! defer std.debug.unlockStdErr(); -//! var stderr = std.fs.File.stderr().writer(&.{}); -//! nosuspend stderr.interface.print(prefix ++ format ++ "\n", args) catch return; -//! } -//! -//! pub fn main() void { -//! // Using the default scope: -//! std.log.debug("A borderline useless debug log message", .{}); // Won't be printed as log_level is .info -//! std.log.info("Flux capacitor is starting to overheat", .{}); -//! -//! // Using scoped logging: -//! const my_project_log = std.log.scoped(.my_project); -//! const nice_library_log = std.log.scoped(.nice_library); -//! const verbose_lib_log = std.log.scoped(.verbose_lib); -//! -//! my_project_log.debug("Starting up", .{}); // Won't be printed as log_level is .info -//! nice_library_log.warn("Something went very wrong, sorry", .{}); -//! 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: -//! ``` -//! [info] (default): Flux capacitor is starting to overheat -//! [warning] (nice_library): Something went very wrong, sorry +//! error: this is an error +//! error(scope): this is an error with a non-default scope +//! warning: this is a warning +//! info: this is an informative message +//! debug: this is a debugging message //! ``` const std = @import("std.zig"); @@ -104,37 +56,28 @@ pub const default_level: Level = switch (builtin.mode) { .ReleaseSafe, .ReleaseFast, .ReleaseSmall => .info, }; -const level = std.options.log_level; - pub const ScopeLevel = struct { scope: @Type(.enum_literal), level: Level, }; -const scope_levels = std.options.log_scope_levels; - fn log( - comptime message_level: Level, + comptime level: Level, comptime scope: @Type(.enum_literal), comptime format: []const u8, args: anytype, ) void { - if (comptime !logEnabled(message_level, scope)) return; + if (comptime !logEnabled(level, scope)) return; - std.options.logFn(message_level, scope, format, args); + std.options.logFn(level, scope, format, args); } /// Determine if a specific log message level and scope combination are enabled for logging. -pub fn logEnabled(comptime message_level: Level, comptime scope: @Type(.enum_literal)) bool { - inline for (scope_levels) |scope_level| { - if (scope_level.scope == scope) return @intFromEnum(message_level) <= @intFromEnum(scope_level.level); +pub fn logEnabled(comptime level: Level, comptime scope: @Type(.enum_literal)) bool { + inline for (std.options.log_scope_levels) |scope_level| { + if (scope_level.scope == scope) return @intFromEnum(level) <= @intFromEnum(scope_level.level); } - return @intFromEnum(message_level) <= @intFromEnum(level); -} - -/// Determine if a specific log message level using the default log scope is enabled for logging. -pub fn defaultLogEnabled(comptime message_level: Level) bool { - return comptime logEnabled(message_level, default_log_scope); + return @intFromEnum(level) <= @intFromEnum(std.options.log_level); } /// The default implementation for the log function. Custom log functions may @@ -143,17 +86,31 @@ pub fn defaultLogEnabled(comptime message_level: Level) bool { /// Uses a 64-byte buffer for formatted printing which is flushed before this /// function returns. pub fn defaultLog( - comptime message_level: Level, + comptime level: Level, comptime scope: @Type(.enum_literal), comptime format: []const u8, args: anytype, ) void { - const level_txt = comptime message_level.asText(); - const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; var buffer: [64]u8 = undefined; - const stderr, _ = std.debug.lockStderrWriter(&buffer); + const stderr, const ttyconf = std.debug.lockStderrWriter(&buffer); defer std.debug.unlockStderrWriter(); - nosuspend stderr.print(level_txt ++ prefix2 ++ format ++ "\n", args) catch return; + ttyconf.setColor(stderr, switch (level) { + .err => .red, + .warn => .yellow, + .info => .green, + .debug => .magenta, + }) catch {}; + ttyconf.setColor(stderr, .bold) catch {}; + stderr.writeAll(level.asText()) catch return; + ttyconf.setColor(stderr, .reset) catch {}; + ttyconf.setColor(stderr, .dim) catch {}; + ttyconf.setColor(stderr, .bold) catch {}; + if (scope != .default) { + stderr.print("({s})", .{@tagName(scope)}) catch return; + } + stderr.writeAll(": ") catch return; + ttyconf.setColor(stderr, .reset) catch {}; + stderr.print(format ++ "\n", args) catch return; } /// Returns a scoped logging namespace that logs all messages using the scope