diff --git a/README.md b/README.md index 060eae5..e87f2d2 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,10 @@ type Logger struct { // deleted.) MaxBackups int `json:"maxbackups" yaml:"maxbackups"` + // ReservedSize is the minimum left space in megabytes of the store device. + // Do not to check the available space of the device. + ReservedSize int `json:"reservedsize" yaml:"reservedsize"` + // LocalTime determines if the time used for formatting the timestamps in // backup files is the computer's local time. The default is to use UTC // time. @@ -107,8 +111,9 @@ number equal to MaxBackups (or all of them if MaxBackups is 0). Any files with an encoded timestamp older than MaxAge days are deleted, regardless of MaxBackups. Note that the time encoded in the timestamp is the rotation time, which may differ from the last time that file was written to. +The older files will be deleted until there's enough ReservedSize space left. -If MaxBackups and MaxAge are both 0, no old log files will be deleted. +If MaxBackups and MaxAge and ReservedSize are all 0, no old log files will be deleted. diff --git a/lumberjack.go b/lumberjack.go index 3447cdc..56ede2f 100644 --- a/lumberjack.go +++ b/lumberjack.go @@ -3,7 +3,7 @@ // Note that this is v2.0 of lumberjack, and should be imported using gopkg.in // thusly: // -// import "gopkg.in/natefinch/lumberjack.v2" +// import "gopkg.in/natefinch/lumberjack.v2" // // The package name remains simply lumberjack, and the code resides at // https://github.com/natefinch/lumberjack under the v2.0 branch. @@ -32,6 +32,7 @@ import ( "sort" "strings" "sync" + "syscall" "time" ) @@ -66,7 +67,7 @@ var _ io.WriteCloser = (*Logger)(nil) // `/var/log/foo/server.log`, a backup created at 6:30pm on Nov 11 2016 would // use the filename `/var/log/foo/server-2016-11-04T18-30-00.000.log` // -// Cleaning Up Old Log Files +// # Cleaning Up Old Log Files // // Whenever a new logfile gets created, old log files may be deleted. The most // recent files according to the encoded timestamp will be retained, up to a @@ -74,8 +75,10 @@ var _ io.WriteCloser = (*Logger)(nil) // with an encoded timestamp older than MaxAge days are deleted, regardless of // MaxBackups. Note that the time encoded in the timestamp is the rotation // time, which may differ from the last time that file was written to. +// The older files will be deleted until there's enough ReservedSize space left. // // If MaxBackups and MaxAge are both 0, no old log files will be deleted. +// If MaxBackups and MaxAge and ReservedSize are all 0, no old log files will be deleted. type Logger struct { // Filename is the file to write logs to. Backup log files will be retained // in the same directory. It uses -lumberjack.log in @@ -98,6 +101,10 @@ type Logger struct { // deleted.) MaxBackups int `json:"maxbackups" yaml:"maxbackups"` + // ReservedSize is the minimum left space in megabytes of the store device. + // Do not to check the available space of the device. + ReservedSize int `json:"reservedsize" yaml:"reservedsize"` + // LocalTime determines if the time used for formatting the timestamps in // backup files is the computer's local time. The default is to use UTC // time. @@ -300,9 +307,9 @@ func (l *Logger) filename() string { // millRunOnce performs compression and removal of stale log files. // Log files are compressed if enabled via configuration and old log // files are removed, keeping at most l.MaxBackups files, as long as -// none of them are older than MaxAge. +// none of them are older than MaxAge, or keep reserved space in device. func (l *Logger) millRunOnce() error { - if l.MaxBackups == 0 && l.MaxAge == 0 && !l.Compress { + if l.MaxBackups == 0 && l.MaxAge == 0 && l.ReservedSize == 0 && !l.Compress { return nil } @@ -347,6 +354,28 @@ func (l *Logger) millRunOnce() error { } files = remaining } + if reserved := l.ReservedSize * megabyte; reserved > 0 && len(files) > 0 { + var fs syscall.Statfs_t + errStat := syscall.Statfs(l.Filename, &fs) + if errStat != nil { + if err == nil { + err = errStat + } + } else { + avail := int(fs.Bavail) * int(fs.Bsize) + var remaining []logInfo + for idx := len(files) - 1; idx >= 0; idx-- { + f := files[idx] + if avail < reserved { + avail += int(f.Size()) + remove = append(remove, f) + } else { + remaining = append(remaining, f) + } + } + files = remaining + } + } if l.Compress { for _, f := range files { diff --git a/lumberjack_test.go b/lumberjack_test.go index f89756c..5664578 100644 --- a/lumberjack_test.go +++ b/lumberjack_test.go @@ -587,6 +587,30 @@ func TestRotate(t *testing.T) { existsWithContent(filename, b2, t) } +func TestReservedSize(t *testing.T) { + currentTime = fakeTime + megabyte = 1 << 20 + dir := makeTempDir("TestReservedSize", t) + defer os.RemoveAll(dir) + + l := &Logger{ + Filename: logFile(dir), + ReservedSize: 1 << 30, // large enough reserved size + LocalTime: true, + } + defer l.Close() + b := []byte("boo!") + n, err := l.Write(b) + isNil(err, t) + equals(len(b), n, t) + + err = l.Rotate() + isNil(err, t) + + <-time.After(20 * time.Millisecond) + notExist(backupFileLocal(dir), t) +} + func TestCompressOnRotate(t *testing.T) { currentTime = fakeTime megabyte = 1