Eric Bower
·
2025-12-05
log.zig
1const std = @import("std");
2
3pub const LogSystem = struct {
4 file: ?std.fs.File = null,
5 mutex: std.Thread.Mutex = .{},
6 current_size: u64 = 0,
7 max_size: u64 = 5 * 1024 * 1024, // 5MB
8 path: []const u8 = "",
9 alloc: std.mem.Allocator = undefined,
10
11 pub fn init(self: *LogSystem, alloc: std.mem.Allocator, path: []const u8) !void {
12 self.alloc = alloc;
13 self.path = try alloc.dupe(u8, path);
14
15 const file = std.fs.openFileAbsolute(path, .{ .mode = .read_write }) catch |err| switch (err) {
16 error.FileNotFound => try std.fs.createFileAbsolute(path, .{ .read = true, .mode = 0o640 }),
17 else => return err,
18 };
19
20 const end_pos = try file.getEndPos();
21 try file.seekTo(end_pos);
22 self.current_size = end_pos;
23 self.file = file;
24 }
25
26 pub fn deinit(self: *LogSystem) void {
27 if (self.file) |f| f.close();
28 if (self.path.len > 0) self.alloc.free(self.path);
29 }
30
31 pub fn log(self: *LogSystem, comptime level: std.log.Level, comptime scope: @Type(.enum_literal), comptime format: []const u8, args: anytype) void {
32 self.mutex.lock();
33 defer self.mutex.unlock();
34
35 if (self.file == null) {
36 std.log.defaultLog(level, scope, format, args);
37 return;
38 }
39
40 if (self.current_size >= self.max_size) {
41 self.rotate() catch |err| {
42 std.debug.print("Log rotation failed: {s}\n", .{@errorName(err)});
43 };
44 }
45
46 const now = std.time.milliTimestamp();
47 const prefix = "[{d}] [{s}] ({s}): ";
48 const scope_name = @tagName(scope);
49 const level_name = level.asText();
50
51 const prefix_args = .{
52 now,
53 level_name,
54 scope_name,
55 };
56
57 if (self.file) |f| {
58 const prefix_len = std.fmt.count(prefix, prefix_args);
59 const msg_len = std.fmt.count(format, args);
60 const newline_len = 1;
61 const total_len = prefix_len + msg_len + newline_len;
62 self.current_size += total_len;
63
64 var buf: [4096]u8 = undefined;
65 var w = f.writerStreaming(&buf);
66 w.interface.print(prefix ++ format ++ "\n", prefix_args ++ args) catch {};
67 w.interface.flush() catch {};
68 }
69 }
70
71 fn rotate(self: *LogSystem) !void {
72 if (self.file) |f| {
73 f.close();
74 self.file = null;
75 }
76
77 const old_path = try std.fmt.allocPrint(self.alloc, "{s}.old", .{self.path});
78 defer self.alloc.free(old_path);
79
80 std.fs.renameAbsolute(self.path, old_path) catch |err| switch (err) {
81 error.FileNotFound => {},
82 else => return err,
83 };
84
85 self.file = try std.fs.createFileAbsolute(self.path, .{ .truncate = true, .read = true, .mode = 0o640 });
86 self.current_size = 0;
87 }
88};