zig-lys/src/log/logging.zig

255 lines
7 KiB
Zig
Raw Normal View History

2024-11-30 23:50:33 +00:00
const std = @import("std");
const cham = @import("chameleon");
2024-12-07 02:29:20 +00:00
const SmartString = @import("../util/utils.zig").SmartString;
2024-12-06 06:31:55 +00:00
2024-11-30 23:50:33 +00:00
const log = std.log;
pub const Level = log.Level;
pub const Scope = @Type(.EnumLiteral);
2024-12-01 02:43:08 +00:00
pub const Color = enum {
red,
green,
yellow,
blue,
magenta,
cyan,
white,
default,
};
pub const ScopeModifier = struct {
2024-12-07 02:29:20 +00:00
scope: SmartString,
2024-12-01 02:43:08 +00:00
color: ?Color = .default,
bright: bool = false,
2024-12-07 02:29:20 +00:00
rename: ?SmartString = null,
2024-12-01 02:43:08 +00:00
};
2024-11-30 23:50:33 +00:00
const Allocator = std.mem.Allocator;
const Globals = struct {
allocator: Allocator,
enableFileOutput: bool = false,
outputFile: ?std.fs.File = null,
2024-12-01 02:43:08 +00:00
additionalScopes: std.ArrayList(ScopeModifier),
fn initOrGetFile(self: *Globals) !std.fs.File {
if (self.outputFile) |file| {
return file;
} else {
return error.NoFileSet;
}
}
2024-11-30 23:50:33 +00:00
fn init(allocator: Allocator) !Globals {
return .{
2024-12-01 02:43:08 +00:00
.allocator = allocator,
2024-12-02 02:50:58 +00:00
.enableFileOutput = false,
.outputFile = null,
2024-12-01 02:43:08 +00:00
.additionalScopes = std.ArrayList(ScopeModifier).init(allocator),
2024-11-30 23:50:33 +00:00
};
}
fn deinit(self: *Globals) void {
if (self.outputFile) |file| {
file.close();
}
2024-12-06 06:31:55 +00:00
for (self.additionalScopes.items) |*modifier| {
if (modifier.*.rename) |*value| {
value.deinit();
2024-12-02 07:16:32 +00:00
}
2024-12-06 06:31:55 +00:00
modifier.*.scope.deinit();
2024-12-02 07:16:32 +00:00
}
2024-12-01 02:43:08 +00:00
self.additionalScopes.deinit();
2024-11-30 23:50:33 +00:00
self.* = undefined;
}
};
var core: ?Globals = null;
pub const config = struct {
pub fn enableFileOutput(value: bool) void {
if (core) |*globals| {
globals.enableFileOutput = value;
} else {
unreachable; // logging is not initialized
}
}
pub fn isFileOutput() bool {
if (core) |*globals| {
return globals.enableFileOutput;
} else {
unreachable; // logging is not initialized
}
}
pub fn setOutputFile(file: std.fs.File) !void {
if (core) |*globals| {
globals.outputFile = file;
} else {
unreachable; // logging is not initialized
}
}
pub fn getOutputFile() ?*const std.fs.File {
if (core) |*globals| {
return &globals.outputFile;
} else {
unreachable; // logging is not initialized
}
}
2024-12-01 02:43:08 +00:00
pub fn addScope(modifier: ScopeModifier) !void {
if (core) |*globals| {
try globals.additionalScopes.append(modifier);
} else {
unreachable; // logging is not initialized
}
}
2024-11-30 23:50:33 +00:00
};
pub fn init(allocator: Allocator) !void {
2024-12-02 02:50:58 +00:00
if (core) |_| {
return error.AlreadyInitialized;
}
2024-11-30 23:50:33 +00:00
core = try Globals.init(allocator);
}
pub fn deinit() void {
if (core) |*globals| {
globals.deinit();
}
}
/// 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 {
nosuspend logFnImpl(level, scope, format, args) catch |err| {
std.debug.print("lys: error while logging: {s}\n", .{@errorName(err)});
};
}
2024-12-02 02:50:58 +00:00
fn get() *Globals {
if (core) |*globals| {
return globals;
2024-12-02 02:33:46 +00:00
}
unreachable; // logging is not initialized
2024-12-01 02:43:08 +00:00
}
2024-11-30 23:50:33 +00:00
fn logFnImpl(comptime level: Level, comptime scope: Scope, comptime format: []const u8, args: anytype) !void {
2024-12-02 02:50:58 +00:00
const globals = get();
2024-12-02 03:16:30 +00:00
var arena = std.heap.ArenaAllocator.init(globals.allocator);
2024-12-02 02:50:58 +00:00
var c = cham.initRuntime(.{
2024-11-30 23:50:33 +00:00
.allocator = arena.allocator(),
});
2024-12-01 02:43:08 +00:00
defer {
c.deinit();
arena.deinit();
}
2024-11-30 23:50:33 +00:00
2024-12-02 02:54:13 +00:00
const scopeText = scopeTextBlk: {
switch (scope) {
.default => break :scopeTextBlk "main",
.gpa => {
2024-12-06 06:39:02 +00:00
const gpa = "GPAlloc";
2024-12-02 02:54:13 +00:00
break :scopeTextBlk try c.redBright().fmt("{s}", .{gpa});
},
else => {
for (globals.additionalScopes.items) |modifier| {
2024-12-06 06:39:02 +00:00
if (std.mem.eql(u8, modifier.scope.data, @tagName(scope))) {
2024-12-02 02:54:13 +00:00
const text = blk: {
if (modifier.rename) |rename| {
2024-12-06 06:39:02 +00:00
break :blk rename.data;
2024-12-02 02:54:13 +00:00
} else {
break :blk @tagName(scope);
}
};
if (modifier.color) |color| {
switch (color) {
.default => break :scopeTextBlk text,
.blue => break :scopeTextBlk try c.blue().fmt("{s}", .{text}),
.green => break :scopeTextBlk try c.green().fmt("{s}", .{text}),
.red => break :scopeTextBlk try c.red().fmt("{s}", .{text}),
.white => break :scopeTextBlk try c.white().fmt("{s}", .{text}),
.yellow => break :scopeTextBlk try c.yellow().fmt("{s}", .{text}),
.magenta => break :scopeTextBlk try c.magenta().fmt("{s}", .{text}),
.cyan => break :scopeTextBlk try c.cyan().fmt("{s}", .{text}),
}
2024-12-01 02:43:08 +00:00
} else {
2024-12-02 02:54:13 +00:00
break :scopeTextBlk text;
2024-12-01 02:43:08 +00:00
}
}
2024-12-02 02:54:52 +00:00
} else {
break :scopeTextBlk @tagName(scope);
2024-12-01 02:43:08 +00:00
}
2024-12-02 02:54:13 +00:00
},
}
unreachable;
2024-12-01 02:43:08 +00:00
};
2024-11-30 23:50:33 +00:00
2024-12-01 02:43:08 +00:00
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"}),
};
2024-12-06 06:39:02 +00:00
const prefix = try std.fmt.allocPrint(arena.allocator(), "[{s}] {s}:", .{
2024-12-01 02:43:08 +00:00
levelText,
scopeText,
});
const message = try std.fmt.allocPrint(arena.allocator(), format, args);
2024-12-02 02:50:58 +00:00
if (globals.enableFileOutput and globals.outputFile != null) {
2024-12-01 02:43:08 +00:00
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,
});
}
2024-11-30 23:50:33 +00:00
}
2024-12-02 02:50:58 +00:00
2024-12-06 06:39:02 +00:00
const t = std.testing;
test "logFn works" {
2024-12-02 02:50:58 +00:00
try init(t.allocator);
defer deinit();
2024-12-06 06:39:02 +00:00
try config.addScope(.{
2024-12-07 02:29:20 +00:00
.scope = SmartString.constant("someScope"),
.rename = SmartString.constant("some rename"),
2024-12-06 06:39:02 +00:00
.color = .green,
});
try config.addScope(.{
2024-12-07 02:29:20 +00:00
.scope = SmartString.constant("other"),
.rename = SmartString.constant("other rename"),
2024-12-06 06:39:02 +00:00
.color = .blue,
});
try logFnImpl(.err, .default, "hello world", .{});
try logFnImpl(.info, .someScope, "hello world", .{});
try logFnImpl(.warn, .other, "hello world", .{});
2024-12-02 02:50:58 +00:00
}