authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-10-28 12:43:13+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-10-30 09:31:30+00:00
log92151216882e2bfacbbb1b6cae3c5281bf9dd03c
tree428d7fac6cec0ed2d33fa2facccb5d43a7e188a3
parent74931fe25cdd94e1cd08b5ece9dcce19959bc079
signaturelock-open Commit is signed but in an unrecognized format.

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 files changed, 34 insertions(+), 77 deletions(-)

lib/std/log.zig+34-77
......@@ -13,63 +13,15 @@
1313//! `const log = std.log.scoped(.libfoo);` to use .libfoo as the scope of its
1414//! log messages.
1515//!
16//! An example `logFn` might look something like this:
17//!
18//! ```
19//! const std = @import("std");
20//!
21//! pub const std_options: std.Options = .{
22//! // Set the log level to info
23//! .log_level = .info,
24//!
25//! // Define logFn to override the std implementation
26//! .logFn = myLogFn,
27//! };
28//!
29//! pub fn myLogFn(
30//! comptime level: std.log.Level,
31//! comptime scope: @Type(.enum_literal),
32//! comptime format: []const u8,
33//! args: anytype,
34//! ) void {
35//! // Ignore all non-error logging from sources other than
36//! // .my_project, .nice_library and the default
37//! const scope_prefix = "(" ++ switch (scope) {
38//! .my_project, .nice_library, std.log.default_log_scope => @tagName(scope),
39//! else => if (@intFromEnum(level) <= @intFromEnum(std.log.Level.err))
40//! @tagName(scope)
41//! else
42//! return,
43//! } ++ "): ";
44//!
45//! const prefix = "[" ++ comptime level.asText() ++ "] " ++ scope_prefix;
46//!
47//! // Print the message to stderr, silently ignoring any errors
48//! std.debug.lockStdErr();
49//! defer std.debug.unlockStdErr();
50//! var stderr = std.fs.File.stderr().writer(&.{});
51//! nosuspend stderr.interface.print(prefix ++ format ++ "\n", args) catch return;
52//! }
53//!
54//! pub fn main() void {
55//! // Using the default scope:
56//! std.log.debug("A borderline useless debug log message", .{}); // Won't be printed as log_level is .info
57//! std.log.info("Flux capacitor is starting to overheat", .{});
58//!
59//! // Using scoped logging:
60//! const my_project_log = std.log.scoped(.my_project);
61//! const nice_library_log = std.log.scoped(.nice_library);
62//! const verbose_lib_log = std.log.scoped(.verbose_lib);
63//!
64//! my_project_log.debug("Starting up", .{}); // Won't be printed as log_level is .info
65//! nice_library_log.warn("Something went very wrong, sorry", .{});
66//! verbose_lib_log.warn("Added 1 + 1: {}", .{1 + 1}); // Won't be printed as it gets filtered out by our log function
67//! }
16//! For an example implementation of the `logFn` function, see `defaultLog`,
17//! which is the default implementation. It outputs to stderr, using color if
18//! the detected `std.Io.tty.Config` supports it. Its output looks like this:
6819//! ```
69//! Which produces the following output:
70//! ```
71//! [info] (default): Flux capacitor is starting to overheat
72//! [warning] (nice_library): Something went very wrong, sorry
20//! error: this is an error
21//! error(scope): this is an error with a non-default scope
22//! warning: this is a warning
23//! info: this is an informative message
24//! debug: this is a debugging message
7325//! ```
7426
7527const std = @import("std.zig");
......@@ -104,37 +56,28 @@ pub const default_level: Level = switch (builtin.mode) {
10456 .ReleaseSafe, .ReleaseFast, .ReleaseSmall => .info,
10557};
10658
107const level = std.options.log_level;
108
10959pub const ScopeLevel = struct {
11060 scope: @Type(.enum_literal),
11161 level: Level,
11262};
11363
114const scope_levels = std.options.log_scope_levels;
115
11664fn log(
117 comptime message_level: Level,
65 comptime level: Level,
11866 comptime scope: @Type(.enum_literal),
11967 comptime format: []const u8,
12068 args: anytype,
12169) void {
122 if (comptime !logEnabled(message_level, scope)) return;
70 if (comptime !logEnabled(level, scope)) return;
12371
124 std.options.logFn(message_level, scope, format, args);
72 std.options.logFn(level, scope, format, args);
12573}
12674
12775/// Determine if a specific log message level and scope combination are enabled for logging.
128pub fn logEnabled(comptime message_level: Level, comptime scope: @Type(.enum_literal)) bool {
129 inline for (scope_levels) |scope_level| {
130 if (scope_level.scope == scope) return @intFromEnum(message_level) <= @intFromEnum(scope_level.level);
76pub fn logEnabled(comptime level: Level, comptime scope: @Type(.enum_literal)) bool {
77 inline for (std.options.log_scope_levels) |scope_level| {
78 if (scope_level.scope == scope) return @intFromEnum(level) <= @intFromEnum(scope_level.level);
13179 }
132 return @intFromEnum(message_level) <= @intFromEnum(level);
133}
134
135/// Determine if a specific log message level using the default log scope is enabled for logging.
136pub fn defaultLogEnabled(comptime message_level: Level) bool {
137 return comptime logEnabled(message_level, default_log_scope);
80 return @intFromEnum(level) <= @intFromEnum(std.options.log_level);
13881}
13982
14083/// The default implementation for the log function. Custom log functions may
......@@ -143,17 +86,31 @@ pub fn defaultLogEnabled(comptime message_level: Level) bool {
14386/// Uses a 64-byte buffer for formatted printing which is flushed before this
14487/// function returns.
14588pub fn defaultLog(
146 comptime message_level: Level,
89 comptime level: Level,
14790 comptime scope: @Type(.enum_literal),
14891 comptime format: []const u8,
14992 args: anytype,
15093) void {
151 const level_txt = comptime message_level.asText();
152 const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
15394 var buffer: [64]u8 = undefined;
154 const stderr, _ = std.debug.lockStderrWriter(&buffer);
95 const stderr, const ttyconf = std.debug.lockStderrWriter(&buffer);
15596 defer std.debug.unlockStderrWriter();
156 nosuspend stderr.print(level_txt ++ prefix2 ++ format ++ "\n", args) catch return;
97 ttyconf.setColor(stderr, switch (level) {
98 .err => .red,
99 .warn => .yellow,
100 .info => .green,
101 .debug => .magenta,
102 }) catch {};
103 ttyconf.setColor(stderr, .bold) catch {};
104 stderr.writeAll(level.asText()) catch return;
105 ttyconf.setColor(stderr, .reset) catch {};
106 ttyconf.setColor(stderr, .dim) catch {};
107 ttyconf.setColor(stderr, .bold) catch {};
108 if (scope != .default) {
109 stderr.print("({s})", .{@tagName(scope)}) catch return;
110 }
111 stderr.writeAll(": ") catch return;
112 ttyconf.setColor(stderr, .reset) catch {};
113 stderr.print(format ++ "\n", args) catch return;
157114}
158115
159116/// Returns a scoped logging namespace that logs all messages using the scope