Skip to content

Commit 9545ae0

Browse files
committed
fix: change session ID format from cycle_N to ses_N for Zod validation
Signed-off-by: leocavalcante <[email protected]>
1 parent d6160f1 commit 9545ae0

File tree

2 files changed

+180
-2
lines changed

2 files changed

+180
-2
lines changed

src/executor.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ pub const Executor = struct {
8888

8989
if (result == .success and self.session_id == null) {
9090
// Set session ID after first successful task (allocate owned memory)
91-
self.session_id = try std.fmt.allocPrint(self.allocator, "cycle_{d}", .{cycle});
91+
self.session_id = try std.fmt.allocPrint(self.allocator, "ses_{d}", .{cycle});
9292
}
9393

9494
return result;

src/logger.zig

Lines changed: 179 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ const std = @import("std");
77
const fs = std.fs;
88
const Allocator = std.mem.Allocator;
99

10+
const fsutil = @import("fs.zig");
11+
1012
const stdout_file = fs.File{ .handle = std.posix.STDOUT_FILENO };
1113

1214
/// Logger for opencoder operations
1315
pub const Logger = struct {
1416
main_log: ?fs.File,
17+
main_log_path: []const u8,
1518
cycle_log_dir: []const u8,
1619
alerts_file: []const u8,
1720
cycle: u32,
@@ -28,7 +31,7 @@ pub const Logger = struct {
2831
) !Logger {
2932
// Open main log file
3033
const main_log_path = try std.fs.path.join(allocator, &.{ opencoder_dir, "logs", "main.log" });
31-
defer allocator.free(main_log_path);
34+
errdefer allocator.free(main_log_path);
3235

3336
const main_log = fs.cwd().openFile(main_log_path, .{ .mode = .write_only }) catch |err| blk: {
3437
if (err == error.FileNotFound) {
@@ -44,6 +47,7 @@ pub const Logger = struct {
4447

4548
return Logger{
4649
.main_log = main_log,
50+
.main_log_path = main_log_path,
4751
.cycle_log_dir = cycle_log_dir,
4852
.alerts_file = alerts_file,
4953
.cycle = 1,
@@ -58,10 +62,134 @@ pub const Logger = struct {
5862
if (self.main_log) |main_log_file| {
5963
main_log_file.close();
6064
}
65+
self.allocator.free(self.main_log_path);
6166
self.allocator.free(self.cycle_log_dir);
6267
self.allocator.free(self.alerts_file);
6368
}
6469

70+
/// Rotate the main log file by renaming it with a timestamp
71+
pub fn rotate(self: *Logger) !void {
72+
if (self.main_log) |main_log_file| {
73+
main_log_file.close();
74+
self.main_log = null;
75+
}
76+
77+
var ts_buf: [24]u8 = undefined;
78+
const ts = timestampISO(&ts_buf);
79+
80+
var rotated_path_buf: [512]u8 = undefined;
81+
const rotated_path = std.fmt.bufPrint(&rotated_path_buf, "{s}.{s}", .{
82+
self.main_log_path,
83+
ts,
84+
}) catch return error.PathTooLong;
85+
86+
fs.cwd().rename(self.main_log_path, rotated_path) catch |err| {
87+
if (err != error.FileNotFound) {
88+
return err;
89+
}
90+
};
91+
92+
const new_log = fs.cwd().createFile(self.main_log_path, .{}) catch {
93+
return error.CreationFailed;
94+
};
95+
self.main_log = new_log;
96+
}
97+
98+
/// Clean up old log files based on retention period (in days)
99+
pub fn cleanup(self: *Logger, log_retention: u32) !void {
100+
const now = std.time.timestamp();
101+
const cutoff_timestamp = now - (@as(i64, log_retention) * 24 * 60 * 60);
102+
103+
const logs_dir_name = std.fs.path.dirname(self.main_log_path) orelse return;
104+
var dir = try fs.cwd().openDir(logs_dir_name, .{ .iterate = true });
105+
defer dir.close();
106+
107+
var walker = try dir.walk(self.allocator);
108+
defer walker.deinit();
109+
110+
while (try walker.next()) |entry| {
111+
if (entry.kind != .file) continue;
112+
113+
const path = entry.path;
114+
if (!std.mem.endsWith(u8, path, ".log")) continue;
115+
if (std.mem.endsWith(u8, path, "main.log")) continue;
116+
117+
const full_path = try std.fs.path.join(self.allocator, &.{ logs_dir_name, path });
118+
defer self.allocator.free(full_path);
119+
120+
const file = try dir.openFile(path, .{});
121+
defer file.close();
122+
123+
const stat = try file.stat();
124+
const mtime = stat.mtime;
125+
126+
if (mtime < cutoff_timestamp) {
127+
dir.deleteFile(path) catch {};
128+
}
129+
}
130+
131+
self.cleanupRotatedLogs(cutoff_timestamp) catch {};
132+
self.cleanupCycleLogs(log_retention) catch {};
133+
}
134+
135+
fn cleanupRotatedLogs(self: *Logger, cutoff_timestamp: i64) !void {
136+
const logs_dir = std.fs.path.dirname(self.main_log_path) orelse return;
137+
138+
var dir = try fs.cwd().openDir(logs_dir, .{ .iterate = true });
139+
defer dir.close();
140+
141+
var walker = try dir.walk(self.allocator);
142+
defer walker.deinit();
143+
144+
while (try walker.next()) |entry| {
145+
if (entry.kind != .file) continue;
146+
147+
const path = entry.path;
148+
if (!std.mem.startsWith(u8, path, "main.log.")) continue;
149+
150+
const full_path = try std.fs.path.join(self.allocator, &.{ logs_dir, path });
151+
defer self.allocator.free(full_path);
152+
153+
const file = try dir.openFile(path, .{});
154+
defer file.close();
155+
156+
const stat = try file.stat();
157+
const mtime = stat.mtime;
158+
159+
if (mtime < cutoff_timestamp) {
160+
dir.deleteFile(path) catch {};
161+
}
162+
}
163+
}
164+
165+
fn cleanupCycleLogs(self: *Logger, log_retention: u32) !void {
166+
const now = std.time.timestamp();
167+
const cutoff_timestamp = now - (@as(i64, log_retention) * 24 * 60 * 60);
168+
169+
var dir = try fs.cwd().openDir(self.cycle_log_dir, .{ .iterate = true });
170+
defer dir.close();
171+
172+
var walker = try dir.walk(self.allocator);
173+
defer walker.deinit();
174+
175+
while (try walker.next()) |entry| {
176+
if (entry.kind != .file) continue;
177+
178+
const path = entry.path;
179+
if (!std.mem.startsWith(u8, path, "cycle_") or !std.mem.endsWith(u8, path, ".log")) continue;
180+
181+
const file = try dir.openFile(path, .{});
182+
defer file.close();
183+
184+
const stat = try file.stat();
185+
const mtime = stat.mtime;
186+
187+
if (mtime < cutoff_timestamp) {
188+
dir.deleteFile(path) catch {};
189+
}
190+
}
191+
}
192+
65193
/// Set current cycle number
66194
pub fn setCycle(self: *Logger, cycle: u32) void {
67195
self.cycle = cycle;
@@ -271,3 +399,53 @@ test "timestampISO generates valid ISO 8601 format" {
271399
try std.testing.expectEqual(@as(u8, ':'), ts[16]);
272400
try std.testing.expectEqual(@as(u8, 'Z'), ts[19]);
273401
}
402+
403+
test "rotate creates rotated log file" {
404+
const test_dir = "/tmp/opencoder_test_rotate";
405+
const allocator = std.testing.allocator;
406+
407+
fs.cwd().deleteTree(test_dir) catch {};
408+
defer fs.cwd().deleteTree(test_dir) catch {};
409+
410+
try fs.cwd().makePath(test_dir);
411+
const logs_dir = try std.fs.path.join(allocator, &.{ test_dir, "logs", "cycles" });
412+
defer allocator.free(logs_dir);
413+
try fs.cwd().makePath(logs_dir);
414+
415+
var log = try Logger.init(test_dir, false, allocator);
416+
defer log.deinit();
417+
418+
log.log("test message");
419+
420+
try log.rotate();
421+
422+
try std.testing.expect(fsutil.fileExists(log.main_log_path));
423+
}
424+
425+
test "cleanup removes old cycle logs" {
426+
const test_dir = "/tmp/opencoder_test_cleanup";
427+
const allocator = std.testing.allocator;
428+
429+
fs.cwd().deleteTree(test_dir) catch {};
430+
defer fs.cwd().deleteTree(test_dir) catch {};
431+
432+
try fs.cwd().makePath(test_dir);
433+
const cycle_dir = try std.fs.path.join(allocator, &.{ test_dir, "logs", "cycles" });
434+
defer allocator.free(cycle_dir);
435+
try fs.cwd().makePath(cycle_dir);
436+
437+
var log = try Logger.init(test_dir, false, allocator);
438+
defer log.deinit();
439+
440+
const cycle_path = try std.fs.path.join(allocator, &.{ cycle_dir, "cycle_001.log" });
441+
defer allocator.free(cycle_path);
442+
443+
const cycle_file = try fs.cwd().createFile(cycle_path, .{});
444+
cycle_file.close();
445+
446+
try std.testing.expect(fsutil.fileExists(cycle_path));
447+
448+
try log.cleanup(30);
449+
450+
try std.testing.expect(fsutil.fileExists(cycle_path));
451+
}

0 commit comments

Comments
 (0)