enhanced logger: add fixed-width [LEVEL] prefixes, introduce fatalLog for .fatal messages, improve alignment and error handling, made fatal noreturn
This commit is contained in:
@@ -10,6 +10,34 @@ pub const Level = enum {
|
||||
warn,
|
||||
@"error",
|
||||
fatal,
|
||||
|
||||
/// Returns the fixed-width, uppercase label used as the log-line
|
||||
/// prefix for `level` (e.g. `"INFO "` for `.info`), derived from
|
||||
/// the enum's own field name via `@tagName`.
|
||||
/// All labels are padded to the width of the longest level name,
|
||||
/// so prefixes line up in a fixed-width terminal/file.
|
||||
fn prefix(level: Level) []const u8
|
||||
{
|
||||
return switch (level)
|
||||
{
|
||||
inline else => |l| comptime comptimeTag(@tagName(l)),
|
||||
};
|
||||
}
|
||||
|
||||
fn comptimeTag(comptime name: []const u8) []const u8
|
||||
{
|
||||
const inner_width = 5;
|
||||
const total_width = inner_width + 2; // account for "[" and "]"
|
||||
|
||||
comptime var buf: [total_width]u8 = .{' '} ** total_width;
|
||||
|
||||
buf[0] = '[';
|
||||
inline for (name, 0..) |c, i| buf[i + 1] = std.ascii.toUpper(c);
|
||||
buf[name.len + 1] = ']';
|
||||
|
||||
const result = buf;
|
||||
return &result;
|
||||
}
|
||||
};
|
||||
|
||||
/// Configuration passed to `Logger.init`.
|
||||
@@ -52,8 +80,8 @@ pub const InitOptions = struct {
|
||||
pub const Logger = struct {
|
||||
pub const ErrorCallback = ?*const fn (err: anyerror) void;
|
||||
|
||||
level: Level = .info,
|
||||
on_error: ErrorCallback = null,
|
||||
level: Level = .info,
|
||||
on_error: ErrorCallback = null,
|
||||
|
||||
io: std.Io,
|
||||
state: *WriterState,
|
||||
@@ -148,26 +176,54 @@ pub const Logger = struct {
|
||||
{
|
||||
if (@intFromEnum(level) < @intFromEnum(logger.level)) return;
|
||||
|
||||
if (level == .fatal) return logger.fatalLog(fmt, args);
|
||||
|
||||
try logger.state.mutex.lock(logger.io);
|
||||
defer logger.state.mutex.unlock(logger.io);
|
||||
|
||||
try logger.state.writer.interface.print(fmt, args);
|
||||
try logger.state.writer.interface.print(
|
||||
"{s} " ++ fmt,
|
||||
.{Level.prefix(level)} ++ args
|
||||
);
|
||||
try logger.state.writer.interface.writeByte('\n');
|
||||
|
||||
switch (level)
|
||||
{
|
||||
.warn, .@"error" => { try logger.state.writer.interface.flush(); },
|
||||
.fatal => { std.debug.panic(fmt, args); },
|
||||
else => return
|
||||
else => return,
|
||||
}
|
||||
}
|
||||
|
||||
/// Best-effort write-then-panic path for `.fatal` messages.
|
||||
///
|
||||
/// Ignores lock/write/flush failures rather than propagating them,
|
||||
/// a failure to persist the fatal message must never prevent the
|
||||
/// panic itself. Marked cold since this is checked on every `log`
|
||||
/// call but taken essentially never.
|
||||
fn fatalLog(logger: *Self, comptime fmt: []const u8, args: anytype) noreturn
|
||||
{
|
||||
@branchHint(.cold);
|
||||
|
||||
logger.state.mutex.lock(logger.io) catch {};
|
||||
|
||||
logger.state.writer.interface.print(
|
||||
"{s} " ++ fmt,
|
||||
.{Level.prefix(.fatal)} ++ args
|
||||
) catch {};
|
||||
|
||||
logger.state.writer.interface.writeByte('\n') catch {};
|
||||
logger.state.writer.interface.flush() catch {};
|
||||
|
||||
std.debug.panic(fmt, args);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
pub fn debug(logger: *Self, comptime fmt: []const u8, args: anytype) void { logger.log(fmt, args, .debug) catch |e| if (logger.on_error) |h| h(e); }
|
||||
pub fn info(logger: *Self, comptime fmt: []const u8, args: anytype) void { logger.log(fmt, args, .info) catch |e| if (logger.on_error) |h| h(e); }
|
||||
pub fn warn(logger: *Self, comptime fmt: []const u8, args: anytype) void { logger.log(fmt, args, .warn) catch |e| if (logger.on_error) |h| h(e); }
|
||||
pub fn @"error"(logger: *Self, comptime fmt: []const u8, args: anytype) void { logger.log(fmt, args, .@"error") catch |e| if (logger.on_error) |h| h(e); }
|
||||
pub fn debug(logger: *Self, comptime fmt: []const u8, args: anytype) void { logger.log(fmt, args, .debug) catch |e| if (logger.on_error) |h| h(e); }
|
||||
pub fn info(logger: *Self, comptime fmt: []const u8, args: anytype) void { logger.log(fmt, args, .info) catch |e| if (logger.on_error) |h| h(e); }
|
||||
pub fn warn(logger: *Self, comptime fmt: []const u8, args: anytype) void { logger.log(fmt, args, .warn) catch |e| if (logger.on_error) |h| h(e); }
|
||||
pub fn @"error"(logger: *Self, comptime fmt: []const u8, args: anytype) void { logger.log(fmt, args, .@"error") catch |e| if (logger.on_error) |h| h(e); }
|
||||
pub fn fatal(logger: *Self, comptime fmt: []const u8, args: anytype) noreturn { logger.log(fmt, args, .fatal) catch unreachable; unreachable; }
|
||||
|
||||
/// Forces any buffered output to be written to the underlying
|
||||
/// writer immediately.
|
||||
@@ -240,7 +296,7 @@ test "filtering, buffering and automatic flushing"
|
||||
|
||||
{
|
||||
const contents = try dir.readFile(io, fname, &read_buf);
|
||||
try std.testing.expectEqualStrings("info=1\n", contents);
|
||||
try std.testing.expectEqualStrings("[INFO] info=1\n", contents);
|
||||
}
|
||||
try file.setLength(io, 0);
|
||||
try log.state.writer.seekTo(0);
|
||||
@@ -251,7 +307,7 @@ test "filtering, buffering and automatic flushing"
|
||||
|
||||
{
|
||||
const contents = try dir.readFile(io, fname, &read_buf);
|
||||
try std.testing.expectEqualStrings("warn=1\n", contents);
|
||||
try std.testing.expectEqualStrings("[WARN] warn=1\n", contents);
|
||||
}
|
||||
try file.setLength(io, 0);
|
||||
try log.state.writer.seekTo(0);
|
||||
@@ -263,7 +319,7 @@ test "filtering, buffering and automatic flushing"
|
||||
{
|
||||
const contents = try dir.readFile(io, fname, &read_buf);
|
||||
// Note: This doesn't expect a newline, as it only drains the actual overflow, not the newline written into the buffer afterwards!
|
||||
try std.testing.expectEqualStrings("info=" ++ ([_]u8{'x'} ** 300), contents);
|
||||
try std.testing.expectEqualStrings("[INFO] info=" ++ ([_]u8{'x'} ** 300), contents);
|
||||
}
|
||||
try file.setLength(io, 0);
|
||||
try log.state.writer.seekTo(0);
|
||||
|
||||
Reference in New Issue
Block a user