diff --git a/klog.go b/klog.go index 69e6e1768..716b1844e 100644 --- a/klog.go +++ b/klog.go @@ -78,6 +78,7 @@ import ( "fmt" "io" stdLog "log" + "math" "os" "path/filepath" "runtime" @@ -410,6 +411,9 @@ func InitFlags(flagset *flag.FlagSet) { } flagset.StringVar(&logging.logDir, "log_dir", "", "If non-empty, write log files in this directory") flagset.StringVar(&logging.logFile, "log_file", "", "If non-empty, use this log file") + flagset.Uint64Var(&logging.logFileMaxSizeMB, "log_file_max_size", 0, + "Defines the maximum size a log file can grow to. Unit is megabytes. "+ + "If the value is 0, the maximum file size is unlimited.") flagset.BoolVar(&logging.toStderr, "logtostderr", true, "log to standard error instead of files") flagset.BoolVar(&logging.alsoToStderr, "alsologtostderr", false, "log to standard error as well as files") flagset.Var(&logging.verbosity, "v", "number for the log level verbosity") @@ -472,6 +476,10 @@ type loggingT struct { // with the log-dir option. logFile string + // When logFile is specified, this limiter makes sure the logFile won't exceeds a certain size. When exceeds, the + // logFile will be cleaned up. If this value is 0, no size limitation will be applied to logFile. + logFileMaxSizeMB uint64 + // If true, do not add the prefix headers, useful when used with SetOutput skipHeaders bool @@ -865,17 +873,33 @@ func (l *loggingT) exit(err error) { type syncBuffer struct { 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 + maxSize uint64 // The max number of bytes this syncBuffer.file can hold before cleaning up. } func (sb *syncBuffer) Sync() error { return sb.file.Sync() } +// CalculateMaxSize returns the real max size after considering the default max size and the flag options. +func CalculateMaxSize() uint64 { + if logging.logFile != "" { + if logging.logFileMaxSizeMB == 0 { + // If logFileMaxSizeMB is zero, we don't have limitations on the log size. + return math.MaxUint64 + } else { + // Flag logFileMaxSizeMB is in MB for user convenience. + return logging.logFileMaxSizeMB * 1024 * 1024 + } + } + // If "log_file" flag is not specified, the target file (sb.file) will be cleaned up when reaches a fixed size. + return MaxSize +} + func (sb *syncBuffer) Write(p []byte) (n int, err error) { - if sb.nbytes+uint64(len(p)) >= MaxSize { + if sb.nbytes+uint64(len(p)) >= sb.maxSize { if err := sb.rotateFile(time.Now(), false); err != nil { sb.logger.exit(err) } @@ -890,7 +914,7 @@ func (sb *syncBuffer) Write(p []byte) (n int, err error) { // rotateFile closes the syncBuffer's file and starts a new one. // The startup argument indicates whether this is the initial startup of klog. -// If startup is true, existing files are opened for apending instead of truncated. +// If startup is true, existing files are opened for appending instead of truncated. func (sb *syncBuffer) rotateFile(now time.Time, startup bool) error { if sb.file != nil { sb.Flush() @@ -933,8 +957,9 @@ func (l *loggingT) createFiles(sev severity) error { // has already been created, we can stop. for s := sev; s >= infoLog && l.file[s] == nil; s-- { sb := &syncBuffer{ - logger: l, - sev: s, + logger: l, + sev: s, + maxSize: CalculateMaxSize(), } if err := sb.rotateFile(now, true); err != nil { return err diff --git a/klog_file.go b/klog_file.go index 1c4897f4f..e4010ad4d 100644 --- a/klog_file.go +++ b/klog_file.go @@ -98,7 +98,7 @@ var onceLogDirs sync.Once // successfully, create also attempts to update the symlink for that tag, ignoring // errors. // The startup argument indicates whether this is the initial startup of klog. -// If startup is true, existing files are opened for apending instead of truncated. +// If startup is true, existing files are opened for appending instead of truncated. func create(tag string, t time.Time, startup bool) (f *os.File, filename string, err error) { if logging.logFile != "" { f, err := openOrCreate(logging.logFile, startup) diff --git a/klog_test.go b/klog_test.go index b5f25c328..64aae4594 100644 --- a/klog_test.go +++ b/klog_test.go @@ -339,7 +339,6 @@ func TestRollover(t *testing.T) { } defer func(previous uint64) { MaxSize = previous }(MaxSize) MaxSize = 512 - Info("x") // Be sure we have a file. info, ok := logging.file[infoLog].(*syncBuffer) if !ok { @@ -370,7 +369,7 @@ func TestRollover(t *testing.T) { if fname0 == fname1 { t.Errorf("info.f.Name did not change: %v", fname0) } - if info.nbytes >= MaxSize { + if info.nbytes >= info.maxSize { t.Errorf("file size was not reset: %d", info.nbytes) } } @@ -487,3 +486,50 @@ func BenchmarkHeader(b *testing.B) { logging.putBuffer(buf) } } + +// Test the logic on checking log size limitation. +func TestFileSizeCheck(t *testing.T) { + setFlags() + testData := map[string]struct { + testLogFile string + testLogFileMaxSizeMB uint64 + testCurrentSize uint64 + expectedResult bool + }{ + "logFile not specified, exceeds max size": { + testLogFile: "", + testLogFileMaxSizeMB: 1, + testCurrentSize: 1024 * 1024 * 2000, //exceeds the maxSize + expectedResult: true, + }, + + "logFile not specified, not exceeds max size": { + testLogFile: "", + testLogFileMaxSizeMB: 1, + testCurrentSize: 1024 * 1024 * 1000, //smaller than the maxSize + expectedResult: false, + }, + "logFile specified, exceeds max size": { + testLogFile: "/tmp/test.log", + testLogFileMaxSizeMB: 500, // 500MB + testCurrentSize: 1024 * 1024 * 1000, //exceeds the logFileMaxSizeMB + expectedResult: true, + }, + "logFile specified, not exceeds max size": { + testLogFile: "/tmp/test.log", + testLogFileMaxSizeMB: 500, // 500MB + testCurrentSize: 1024 * 1024 * 300, //smaller than the logFileMaxSizeMB + expectedResult: false, + }, + } + + for name, test := range testData { + logging.logFile = test.testLogFile + logging.logFileMaxSizeMB = test.testLogFileMaxSizeMB + actualResult := test.testCurrentSize >= CalculateMaxSize() + if test.expectedResult != actualResult { + t.Fatalf("Error on test case '%v': Was expecting result equals %v, got %v", + name, test.expectedResult, actualResult) + } + } +}