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:
@@ -811,11 +811,12 @@ func (l *loggingT) exit(err error) {
|
||||
// file rotation. There are conflicting methods, so the file cannot be embedded.
|
||||
// l.mu is held for all its methods.
|
||||
type syncBuffer struct {
|
||||
logger *loggingT
|
||||
logger *loggingT
|
||||
*bufio.Writer
|
||||
file *os.File
|
||||
sev severity
|
||||
nbytes uint64 // The number of bytes written to this file
|
||||
file *os.File
|
||||
sev severity
|
||||
nbytes uint64 // The number of bytes written to this file
|
||||
createdAt time.Time // When the current log file was opened (used for time-based rotation)
|
||||
}
|
||||
|
||||
func (sb *syncBuffer) Sync() error {
|
||||
@@ -830,8 +831,14 @@ func (sb *syncBuffer) Write(p []byte) (n int, err error) {
|
||||
if sb.Writer == nil {
|
||||
return 0, errors.New("log writer is nil")
|
||||
}
|
||||
if sb.nbytes+uint64(len(p)) >= MaxSize {
|
||||
if err := sb.rotateFile(time.Now()); err != nil {
|
||||
now := timeNow()
|
||||
// Size-based rotation: rotate when the file would exceed MaxSize.
|
||||
sizeRotation := sb.nbytes+uint64(len(p)) >= MaxSize
|
||||
// Time-based rotation: rotate when the file is older than --log_rotate_hours.
|
||||
h := LogRotateHours()
|
||||
timeRotation := h > 0 && !sb.createdAt.IsZero() && now.Sub(sb.createdAt) >= time.Duration(h)*time.Hour
|
||||
if sizeRotation || timeRotation {
|
||||
if err := sb.rotateFile(now); err != nil {
|
||||
sb.logger.exit(err)
|
||||
return 0, err
|
||||
}
|
||||
@@ -853,6 +860,7 @@ func (sb *syncBuffer) rotateFile(now time.Time) error {
|
||||
var err error
|
||||
sb.file, _, err = create(severityName[sb.sev], now)
|
||||
sb.nbytes = 0
|
||||
sb.createdAt = now
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user