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.
This commit is contained in:
Matthew Lugg 2025-10-28 12:43:13 +00:00
parent 74931fe25c
commit 9215121688
No known key found for this signature in database
GPG Key ID: 3F5B7DCCBF4AF02E

View File

@ -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