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,
|
warn,
|
||||||
@"error",
|
@"error",
|
||||||
fatal,
|
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`.
|
/// Configuration passed to `Logger.init`.
|
||||||
@@ -148,26 +176,54 @@ pub const Logger = struct {
|
|||||||
{
|
{
|
||||||
if (@intFromEnum(level) < @intFromEnum(logger.level)) return;
|
if (@intFromEnum(level) < @intFromEnum(logger.level)) return;
|
||||||
|
|
||||||
|
if (level == .fatal) return logger.fatalLog(fmt, args);
|
||||||
|
|
||||||
try logger.state.mutex.lock(logger.io);
|
try logger.state.mutex.lock(logger.io);
|
||||||
defer logger.state.mutex.unlock(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');
|
try logger.state.writer.interface.writeByte('\n');
|
||||||
|
|
||||||
switch (level)
|
switch (level)
|
||||||
{
|
{
|
||||||
.warn, .@"error" => { try logger.state.writer.interface.flush(); },
|
.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 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 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 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 @"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
|
/// Forces any buffered output to be written to the underlying
|
||||||
/// writer immediately.
|
/// writer immediately.
|
||||||
@@ -240,7 +296,7 @@ test "filtering, buffering and automatic flushing"
|
|||||||
|
|
||||||
{
|
{
|
||||||
const contents = try dir.readFile(io, fname, &read_buf);
|
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 file.setLength(io, 0);
|
||||||
try log.state.writer.seekTo(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);
|
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 file.setLength(io, 0);
|
||||||
try log.state.writer.seekTo(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);
|
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!
|
// 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 file.setLength(io, 0);
|
||||||
try log.state.writer.seekTo(0);
|
try log.state.writer.seekTo(0);
|
||||||
|
|||||||
Reference in New Issue
Block a user