glog: add --log_rotate_hours flag for time-based log rotation (#8685)
* glog: add --log_rotate_hours flag for time-based log rotation SeaweedFS previously only rotated log files when they reached MaxSize (1.8 GB). Long-running deployments with low log volume could accumulate log files indefinitely with no way to force rotation on a schedule. This change adds the --log_rotate_hours flag. When set to a non-zero value, the current log file is rotated once it has been open for the specified number of hours, regardless of its size. Implementation details: - New flag --log_rotate_hours (int, default 0 = disabled) in glog_file.go - Added createdAt time.Time field to syncBuffer to track file open time - rotateFile() sets createdAt to the time the new file is opened - Write() checks elapsed time and triggers rotation when the threshold is exceeded, consistent with the existing size-based check This resolves the long-standing request for time-based rotation and helps prevent unbounded log accumulation in /tmp on production systems. Related: #3455, #5763, #8336 * glog: default log_rotate_hours to 168 (7 days) Enable time-based rotation by default so log files don't accumulate indefinitely in long-running deployments. Set to 0 to disable. * glog: simplify rotation logic by combining size and time conditions Merge the two separate rotation checks into a single block to eliminate duplicated rotateFile error handling. * glog: use timeNow() in syncBuffer.Write and add time-based rotation test Use the existing testable timeNow variable instead of time.Now() in syncBuffer.Write so that time-based rotation can be tested with a mocked clock. Add TestTimeBasedRollover that verifies: - no rotation occurs before the interval elapses - rotation triggers after the configured hours --------- Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -371,6 +371,56 @@ func TestRollover(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeBasedRollover(t *testing.T) {
|
||||
setFlags()
|
||||
var err error
|
||||
defer func(previous func(error)) { logExitFunc = previous }(logExitFunc)
|
||||
logExitFunc = func(e error) {
|
||||
err = e
|
||||
}
|
||||
|
||||
// Disable size-based rotation by setting a very large MaxSize.
|
||||
defer func(previous uint64) { MaxSize = previous }(MaxSize)
|
||||
MaxSize = 1024 * 1024 * 1024
|
||||
|
||||
// Enable time-based rotation with a 1-hour interval.
|
||||
defer func(previous int) { *logRotateHours = previous }(*logRotateHours)
|
||||
*logRotateHours = 1
|
||||
|
||||
Info("x") // Create initial file.
|
||||
info, ok := logging.file[infoLog].(*syncBuffer)
|
||||
if !ok {
|
||||
t.Fatal("info wasn't created")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("info has initial error: %v", err)
|
||||
}
|
||||
fname0 := info.file.Name()
|
||||
createdAt := info.createdAt
|
||||
|
||||
// Mock time to 30 minutes after file creation — should NOT rotate.
|
||||
defer func(previous func() time.Time) { timeNow = previous }(timeNow)
|
||||
timeNow = func() time.Time { return createdAt.Add(30 * time.Minute) }
|
||||
Info("still within interval")
|
||||
if err != nil {
|
||||
t.Fatalf("error after write within interval: %v", err)
|
||||
}
|
||||
if info.file.Name() != fname0 {
|
||||
t.Error("file rotated before interval elapsed")
|
||||
}
|
||||
|
||||
// Advance mock time past the 1-hour interval — should rotate.
|
||||
timeNow = func() time.Time { return createdAt.Add(61 * time.Minute) }
|
||||
Info("past interval")
|
||||
if err != nil {
|
||||
t.Fatalf("error after time-based rotation: %v", err)
|
||||
}
|
||||
fname1 := info.file.Name()
|
||||
if fname0 == fname1 {
|
||||
t.Error("file did not rotate after interval elapsed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogBacktraceAt(t *testing.T) {
|
||||
setFlags()
|
||||
defer logging.swap(logging.newBuffers())
|
||||
|
||||
Reference in New Issue
Block a user