Skip to content

Commit

Permalink
Add a log-file-max-size flag to fix large logfile (>1.8GB) truncation.
Browse files Browse the repository at this point in the history
  • Loading branch information
yuwenma committed Apr 10, 2019
1 parent aaeac2f commit b6ecd14
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 10 deletions.
39 changes: 32 additions & 7 deletions klog.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import (
"fmt"
"io"
stdLog "log"
"math"
"os"
"path/filepath"
"runtime"
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
}
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion klog_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
50 changes: 48 additions & 2 deletions klog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -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)
}
}
}

0 comments on commit b6ecd14

Please sign in to comment.