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