mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-04-28 03:17:08 +03:00
ffcbd48a12
This commit sketches an idea for how to deal with detection of file streams as being terminals. When a File stream is a terminal, writes through the stream should have their escapes stripped unless the programmer explicitly enables terminal escapes. Furthermore, the programmer needs a convenient API for intentionally outputting escapes into the stream. In particular it should be possible to set colors that are silently discarded when the stream is not a terminal. This commit makes `Io.File.Writer` track the terminal mode in the already-existing `mode` field, making it the appropriate place to implement escape stripping. `Io.lockStderrWriter` returns a `*Io.File.Writer` with terminal detection already done by default. This is a higher-level application layer stream for writing to stderr. Meanwhile, `std.debug.lockStderrWriter` also returns a `*Io.File.Writer` but a lower-level one that is hard-coded to use a static single-threaded `std.Io.Threaded` instance. This is the same instance that is used for collecting debug information and iterating the unwind info.
221 lines
7.5 KiB
Zig
221 lines
7.5 KiB
Zig
//! std.log is a standardized interface for logging which allows for the logging
|
|
//! of programs and libraries using this interface to be formatted and filtered
|
|
//! by the implementer of the `std.options.logFn` function.
|
|
//!
|
|
//! Each log message has an associated scope enum, which can be used to give
|
|
//! context to the logging. The logging functions in std.log implicitly use a
|
|
//! scope of .default.
|
|
//!
|
|
//! A logging namespace using a custom scope can be created using the
|
|
//! std.log.scoped function, passing the scope as an argument; the logging
|
|
//! functions in the resulting struct use the provided scope parameter.
|
|
//! For example, a library called 'libfoo' might use
|
|
//! `const log = std.log.scoped(.libfoo);` to use .libfoo as the scope of its
|
|
//! log messages.
|
|
//!
|
|
//! For an example implementation of the `logFn` function, see `defaultLog`,
|
|
//! which is the default implementation. It outputs to stderr, using color if
|
|
//! supported. Its output looks like this:
|
|
//! ```
|
|
//! 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");
|
|
const builtin = @import("builtin");
|
|
|
|
pub const Level = enum {
|
|
/// Error: something has gone wrong. This might be recoverable or might
|
|
/// be followed by the program exiting.
|
|
err,
|
|
/// Warning: it is uncertain if something has gone wrong or not, but the
|
|
/// circumstances would be worth investigating.
|
|
warn,
|
|
/// Info: general messages about the state of the program.
|
|
info,
|
|
/// Debug: messages only useful for debugging.
|
|
debug,
|
|
|
|
/// Returns a string literal of the given level in full text form.
|
|
pub fn asText(comptime self: Level) []const u8 {
|
|
return switch (self) {
|
|
.err => "error",
|
|
.warn => "warning",
|
|
.info => "info",
|
|
.debug => "debug",
|
|
};
|
|
}
|
|
};
|
|
|
|
/// The default log level is based on build mode.
|
|
pub const default_level: Level = switch (builtin.mode) {
|
|
.Debug => .debug,
|
|
.ReleaseSafe, .ReleaseFast, .ReleaseSmall => .info,
|
|
};
|
|
|
|
pub const ScopeLevel = struct {
|
|
scope: @EnumLiteral(),
|
|
level: Level,
|
|
};
|
|
|
|
fn log(
|
|
comptime level: Level,
|
|
comptime scope: @EnumLiteral(),
|
|
comptime format: []const u8,
|
|
args: anytype,
|
|
) void {
|
|
if (comptime !logEnabled(level, scope)) return;
|
|
|
|
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 level: Level, comptime scope: @EnumLiteral()) bool {
|
|
inline for (std.options.log_scope_levels) |scope_level| {
|
|
if (scope_level.scope == scope) return @intFromEnum(level) <= @intFromEnum(scope_level.level);
|
|
}
|
|
return @intFromEnum(level) <= @intFromEnum(std.options.log_level);
|
|
}
|
|
|
|
/// The default implementation for the log function. Custom log functions may
|
|
/// forward log messages to this function.
|
|
///
|
|
/// Uses a 64-byte buffer for formatted printing which is flushed before this
|
|
/// function returns.
|
|
pub fn defaultLog(
|
|
comptime level: Level,
|
|
comptime scope: @EnumLiteral(),
|
|
comptime format: []const u8,
|
|
args: anytype,
|
|
) void {
|
|
var buffer: [64]u8 = undefined;
|
|
const stderr = std.debug.lockStderrWriter(&buffer);
|
|
defer std.debug.unlockStderrWriter();
|
|
return defaultLogFileWriter(level, scope, format, args, stderr);
|
|
}
|
|
|
|
pub fn defaultLogFileWriter(
|
|
comptime level: Level,
|
|
comptime scope: @EnumLiteral(),
|
|
comptime format: []const u8,
|
|
args: anytype,
|
|
fw: *std.Io.File.Writer,
|
|
) void {
|
|
fw.setColor(switch (level) {
|
|
.err => .red,
|
|
.warn => .yellow,
|
|
.info => .green,
|
|
.debug => .magenta,
|
|
}) catch {};
|
|
fw.setColor(.bold) catch {};
|
|
fw.interface.writeAll(level.asText()) catch return;
|
|
fw.setColor(.reset) catch {};
|
|
fw.setColor(.dim) catch {};
|
|
fw.setColor(.bold) catch {};
|
|
if (scope != .default) {
|
|
fw.interface.print("({s})", .{@tagName(scope)}) catch return;
|
|
}
|
|
fw.interface.writeAll(": ") catch return;
|
|
fw.setColor(.reset) catch {};
|
|
fw.interface.print(format ++ "\n", decorateArgs(args, fw.mode)) catch return;
|
|
}
|
|
|
|
fn DecorateArgs(comptime Args: type) type {
|
|
const fields = @typeInfo(Args).@"struct".fields;
|
|
var new_fields: [fields.len]type = undefined;
|
|
for (fields, &new_fields) |old, *new| {
|
|
if (old.type == std.debug.FormatStackTrace) {
|
|
new.* = std.debug.FormatStackTrace.Decorated;
|
|
} else {
|
|
new.* = old.type;
|
|
}
|
|
}
|
|
return @Tuple(&new_fields);
|
|
}
|
|
|
|
fn decorateArgs(args: anytype, file_writer_mode: std.Io.File.Writer.Mode) DecorateArgs(@TypeOf(args)) {
|
|
var new_args: DecorateArgs(@TypeOf(args)) = undefined;
|
|
inline for (args, &new_args) |old, *new| {
|
|
if (@TypeOf(old) == std.debug.FormatStackTrace) {
|
|
new.* = .{
|
|
.stack_trace = old.stack_trace,
|
|
.file_writer_mode = file_writer_mode,
|
|
};
|
|
} else {
|
|
new.* = old;
|
|
}
|
|
}
|
|
return new_args;
|
|
}
|
|
|
|
/// Returns a scoped logging namespace that logs all messages using the scope
|
|
/// provided here.
|
|
pub fn scoped(comptime scope: @EnumLiteral()) type {
|
|
return struct {
|
|
/// Log an error message. This log level is intended to be used
|
|
/// when something has gone wrong. This might be recoverable or might
|
|
/// be followed by the program exiting.
|
|
pub fn err(
|
|
comptime format: []const u8,
|
|
args: anytype,
|
|
) void {
|
|
@branchHint(.cold);
|
|
log(.err, scope, format, args);
|
|
}
|
|
|
|
/// Log a warning message. This log level is intended to be used if
|
|
/// it is uncertain whether something has gone wrong or not, but the
|
|
/// circumstances would be worth investigating.
|
|
pub fn warn(
|
|
comptime format: []const u8,
|
|
args: anytype,
|
|
) void {
|
|
log(.warn, scope, format, args);
|
|
}
|
|
|
|
/// Log an info message. This log level is intended to be used for
|
|
/// general messages about the state of the program.
|
|
pub fn info(
|
|
comptime format: []const u8,
|
|
args: anytype,
|
|
) void {
|
|
log(.info, scope, format, args);
|
|
}
|
|
|
|
/// Log a debug message. This log level is intended to be used for
|
|
/// messages which are only useful for debugging.
|
|
pub fn debug(
|
|
comptime format: []const u8,
|
|
args: anytype,
|
|
) void {
|
|
log(.debug, scope, format, args);
|
|
}
|
|
};
|
|
}
|
|
|
|
pub const default_log_scope = .default;
|
|
|
|
/// The default scoped logging namespace.
|
|
pub const default = scoped(default_log_scope);
|
|
|
|
/// Log an error message using the default scope. This log level is intended to
|
|
/// be used when something has gone wrong. This might be recoverable or might
|
|
/// be followed by the program exiting.
|
|
pub const err = default.err;
|
|
|
|
/// Log a warning message using the default scope. This log level is intended
|
|
/// to be used if it is uncertain whether something has gone wrong or not, but
|
|
/// the circumstances would be worth investigating.
|
|
pub const warn = default.warn;
|
|
|
|
/// 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.
|
|
pub const info = default.info;
|
|
|
|
/// Log a debug message using the default scope. This log level is intended to
|
|
/// be used for messages which are only useful for debugging.
|
|
pub const debug = default.debug;
|