Commit 9215121688

Matthew Lugg <mlugg@mlugg.co.uk>
2025-10-28 13:43:13
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.
1 parent 74931fe
Changed files (1)
lib
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:
-//!
-//! ```
-//! 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
-//! }
+//! 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:
 //! ```
-//! 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