diff --git a/src/log/logging.zig b/src/log/logging.zig index 1918397..280fe4a 100644 --- a/src/log/logging.zig +++ b/src/log/logging.zig @@ -6,6 +6,24 @@ const log = std.log; pub const Level = log.Level; pub const Scope = @Type(.EnumLiteral); +pub const Color = enum { + red, + green, + yellow, + blue, + magenta, + cyan, + white, + default, +}; + +pub const ScopeModifier = struct { + scope: Scope, + color: ?Color = .default, + bright: bool = false, + rename: ?[]const u8 = null, +}; + const Allocator = std.mem.Allocator; const Globals = struct { @@ -14,13 +32,25 @@ const Globals = struct { enableFileOutput: bool = false, outputFile: ?std.fs.File = null, + additionalScopes: std.ArrayList(ScopeModifier), + fn arena(self: *Globals) std.heap.ArenaAllocator { return std.heap.ArenaAllocator.init(self.allocator); } + fn initOrGetFile(self: *Globals) !std.fs.File { + if (self.outputFile) |file| { + return file; + } else { + return error.NoFileSet; + } + } + fn init(allocator: Allocator) !Globals { return .{ - allocator, + .allocator = allocator, + + .additionalScopes = std.ArrayList(ScopeModifier).init(allocator), }; } @@ -29,6 +59,8 @@ const Globals = struct { file.close(); } + self.additionalScopes.deinit(); + self.* = undefined; } }; @@ -67,6 +99,14 @@ pub const config = struct { unreachable; // logging is not initialized } } + + pub fn addScope(modifier: ScopeModifier) !void { + if (core) |*globals| { + try globals.additionalScopes.append(modifier); + } else { + unreachable; // logging is not initialized + } + } }; pub fn init(allocator: Allocator) !void { @@ -79,6 +119,8 @@ pub fn deinit() void { } } +var longestScopeYet: usize = 0; + /// If using this log function, you *must* call `init` before any logging occurs. /// Otherwise, it will complain. A lot. pub fn logFn(comptime level: Level, comptime scope: Scope, comptime format: []const u8, args: anytype) void { @@ -87,18 +129,97 @@ pub fn logFn(comptime level: Level, comptime scope: Scope, comptime format: []co }; } +fn get() !*Globals { + return &core orelse error.NotInitialized; +} + fn logFnImpl(comptime level: Level, comptime scope: Scope, comptime format: []const u8, args: anytype) !void { - var arena = core.?.arena(); + const globals = try get(); + var arena = globals.arena(); const c = cham.initRuntime(.{ .allocator = arena.allocator(), }); + defer { + c.deinit(); + arena.deinit(); + } - _ = c; + var scopeLen = 4; + const scopeText = switch (scope) { + .default => "main", + .gpa => gpaBlock: { + const gpa = "General Purpose Allocator"; + scopeLen = gpa; - _ = level; - _ = scope; - _ = format; - _ = args; + break :gpaBlock try c.redBright().fmt("{s}", .{gpa}); + }, - return error.NotImplemented; + else => elseBlock: { + for (globals.additionalScopes.items) |modifier| { + if (modifier.scope == scope) { + const text = blk: { + if (modifier.rename) |rename| { + break :blk rename; + } else { + break :blk @tagName(scope); + } + }; + + scopeLen = text.len; + + if (modifier.color) |color| { + switch (color) { + .default => break :elseBlock text, + .blue => break :elseBlock try c.blue().fmt("{s}", .{text}), + .green => break :elseBlock try c.green().fmt("{s}", .{text}), + .red => break :elseBlock try c.red().fmt("{s}", .{text}), + .white => break :elseBlock try c.white().fmt("{s}", .{text}), + .yellow => break :elseBlock try c.yellow().fmt("{s}", .{text}), + .magenta => break :elseBlock try c.magenta().fmt("{s}", .{text}), + .cyan => break :elseBlock try c.cyan().fmt("{s}", .{text}), + } + } else { + break :elseBlock text; + } + } + } + }, + }; + + longestScopeYet = std.math.max(longestScopeYet, scopeLen); + + const levelText = switch (level) { + .debug => try c.gray().fmt("{s: >5}", .{"DEBUG"}), + .info => try c.white().fmt("{s: >5}", .{"INFO"}), + .warn => try c.yellow().fmt("{s: >5}", .{"WARN"}), + .err => try c.red().fmt("{s: >5}", .{"ERROR"}), + }; + + const paddingLen = longestScopeYet - scopeLen; + + const padding = try arena.allocator().alloc(u8, paddingLen); + for (padding) |*p| p.* = ' '; + + const prefix = try std.fmt.allocPrint(arena.allocator(), "[{s}] [{s}{s}]", .{ + levelText, + padding, + scopeText, + }); + + const message = try std.fmt.allocPrint(arena.allocator(), format, args); + + if (globals.enableFileOutput) { + var file = try globals.initOrGetFile(); + var writer = file.writer().any(); + + nosuspend try writer.print("{s} {s}\n", .{ + prefix, + message, + }); + } else { + nosuspend std.debug.print("{s} {s}\n", .{ + prefix, + message, + }); + } }