From 72cc861377da1be2ae12447c8b92fd39a95a5242 Mon Sep 17 00:00:00 2001 From: Nate Finch Date: Wed, 18 Jun 2014 08:09:40 -0400 Subject: [PATCH] Change definition of maxage to make it easier (i.e. possible) to load from a json/yaml/etc --- README.md | 16 ++++++-------- example_test.go | 2 +- lumberjack.go | 36 ++++++++++++++++++------------ lumberjack_test.go | 55 ++++++++++++++++++++++++++++++++-------------- 4 files changed, 69 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 6dec83a..974bae4 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ log.SetOutput(&lumberjack.Logger{ NameFormat: time.RFC822 + ".log", MaxSize: lumberjack.Gigabyte, MaxBackups: 3, - MaxAge: lumberjack.Week * 4, + MaxAge: 28, }) ``` @@ -39,9 +39,6 @@ log.SetOutput(&lumberjack.Logger{ const ( Megabyte = 1024 * 1024 Gigabyte = 1024 * Megabyte - - Day = 24 * time.Hour - Week = 7 * Day ) ``` @@ -62,10 +59,11 @@ type Logger struct { // rolled. It defaults to 100 megabytes. MaxSize int64 `json:"maxsize" yaml:"maxsize"` - // MaxAge is the maximum time to retain old log files based on - // FileInfo.ModTime. The default is not to remove old log files based on - // age. - MaxAge time.Duration `json:"maxage" yaml:"maxage"` + // MaxAge is the maximum number of days to retain old log files based on + // FileInfo.ModTime. Note that a day is defined as 24 hours and may not + // exactly correspond to calendar days due to daylight savings, leap + // seconds, etc. The default is not to remove old log files based on age. + MaxAge int `json:"maxage" yaml:"maxage"` // MaxBackups is the maximum number of old log files to retain. The default // is to retain all old log files (though MaxAge may still cause them to get @@ -100,7 +98,7 @@ Whenever a new file gets created, old log files may be deleted. The log file directory is scanned for files that match NameFormat. The most recent files according to their NameFormat date will be retained, up to a number equal to MaxBackups (or all of them if MaxBackups is 0). Any files with a last -modified time (based on FileInfo.ModTime) older than MaxAge are deleted, +modified time (based on FileInfo.ModTime) older than MaxAge days are deleted, regardless of MaxBackups. If MaxBackups and MaxAge are both 0, no old log files will be deleted. diff --git a/example_test.go b/example_test.go index 2a5ef39..d2abe68 100644 --- a/example_test.go +++ b/example_test.go @@ -35,6 +35,6 @@ func Example() { NameFormat: time.RFC822 + ".log", MaxSize: lumberjack.Gigabyte, MaxBackups: 3, - MaxAge: lumberjack.Week * 4, + MaxAge: 28, }) } diff --git a/lumberjack.go b/lumberjack.go index b436e16..b64f505 100644 --- a/lumberjack.go +++ b/lumberjack.go @@ -30,12 +30,6 @@ const ( Megabyte = 1024 * 1024 Gigabyte = 1024 * Megabyte - // Note that lumberjack days and weeks may not exactly conform to calendar - // days and weeks due to daylight savings, leap seconds, etc. - - Day = 24 * time.Hour - Week = 7 * Day - defaultNameFormat = "2006-01-02T15-04-05.000.log" defaultMaxSize = 100 * Megabyte ) @@ -66,7 +60,7 @@ var _ io.WriteCloser = (*Logger)(nil) // directory is scanned for files that match NameFormat. The most recent files // according to their NameFormat date will be retained, up to a number equal to // MaxBackups (or all of them if MaxBackups is 0). Any files with a last -// modified time (based on FileInfo.ModTime) older than MaxAge are deleted, +// modified time (based on FileInfo.ModTime) older than MaxAge days are deleted, // regardless of MaxBackups. // // If MaxBackups and MaxAge are both 0, no old log files will be deleted. @@ -83,10 +77,11 @@ type Logger struct { // rolled. It defaults to 100 megabytes. MaxSize int64 `json:"maxsize" yaml:"maxsize"` - // MaxAge is the maximum time to retain old log files based on - // FileInfo.ModTime. The default is not to remove old log files based on - // age. - MaxAge time.Duration `json:"maxage" yaml:"maxage"` + // MaxAge is the maximum number of days to retain old log files based on + // FileInfo.ModTime. Note that a day is defined as 24 hours and may not + // exactly correspond to calendar days due to daylight savings, leap + // seconds, etc. The default is not to remove old log files based on age. + MaxAge int `json:"maxage" yaml:"maxage"` // MaxBackups is the maximum number of old log files to retain. The default // is to retain all old log files (though MaxAge may still cause them to get @@ -102,8 +97,14 @@ type Logger struct { mu sync.Mutex } -// currentTime is only used for testing. Normally it's the time.Now() function. -var currentTime = time.Now +var ( + // currentTime exists so it can be mocked out by tests. + currentTime = time.Now + + // day is the units that MaxAge converts into. It is a variable so we can + // change it during tests. + day = 24 * time.Hour +) // Write implements io.Writer. If a write would cause the log file to be larger // than MaxSize, a new log file is created using the current time formatted with @@ -269,7 +270,9 @@ func (l *Logger) cleanup() error { files = files[:l.MaxBackups] } if l.MaxAge > 0 { - cutoff := currentTime().Add(-1 * l.MaxAge) + diff := time.Duration(-1 * int64(day) * int64(l.MaxAge)) + + cutoff := currentTime().Add(diff) for _, f := range files { if f.ModTime().Before(cutoff) { @@ -322,11 +325,14 @@ func (l *Logger) oldLogFiles() ([]os.FileInfo, error) { return logFiles, nil } +// isLogFile reports where the name of f fits the format of a logfile created by +// this Logger. func (l *Logger) isLogFile(f os.FileInfo) bool { _, err := time.Parse(l.format(), filepath.Base(f.Name())) return err == nil } +// max returns the maximum size of log files before rolling.. func (l *Logger) max() int64 { if l.MaxSize == 0 { return defaultMaxSize @@ -334,6 +340,7 @@ func (l *Logger) max() int64 { return l.MaxSize } +// dir returns the directory in which log files should be stored. func (l *Logger) dir() string { if l.Dir != "" { return l.Dir @@ -341,6 +348,7 @@ func (l *Logger) dir() string { return os.TempDir() } +// format returns the name format for log files. func (l *Logger) format() string { if l.NameFormat != "" { return l.NameFormat diff --git a/lumberjack_test.go b/lumberjack_test.go index ac443c2..1e706b6 100644 --- a/lumberjack_test.go +++ b/lumberjack_test.go @@ -1,6 +1,7 @@ package lumberjack import ( + "encoding/json" "fmt" "io/ioutil" "os" @@ -166,7 +167,7 @@ func TestAutoRotate(t *testing.T) { fileCount(dir, 1, t) // set the current time one day later - defer newFakeTime(Day)() + newFakeTime() b2 := []byte("foooooo!") n, err = l.Write(b2) @@ -200,7 +201,7 @@ func TestFirstWriteRotate(t *testing.T) { isNil(err, t) // set the current time one day later - defer newFakeTime(Day)() + newFakeTime() // this would make us rotate b := []byte("fooo!") @@ -236,7 +237,7 @@ func TestMaxBackups(t *testing.T) { fileCount(dir, 1, t) // set the current time one day later - defer newFakeTime(Day)() + newFakeTime() // this will put us over the max b2 := []byte("foooooo!") @@ -254,7 +255,7 @@ func TestMaxBackups(t *testing.T) { fileCount(dir, 2, t) // set the current time one day later - defer newFakeTime(Day)() + newFakeTime() // this will make us rotate again n, err = l.Write(b2) @@ -281,7 +282,7 @@ func TestMaxBackups(t *testing.T) { // now test that we don't delete directories or non-logfile files // set the current time one day later - defer newFakeTime(Day)() + newFakeTime() // create a file that is close to but different from the logfile name. /// It shouldn't get caught by our deletion filters. @@ -295,7 +296,7 @@ func TestMaxBackups(t *testing.T) { err = os.Mkdir(notlogfiledir, 0700) isNil(err, t) - defer newFakeTime(Day)() + newFakeTime() // this will make us rotate again n, err = l.Write(b2) @@ -329,6 +330,10 @@ func TestMaxBackups(t *testing.T) { func TestMaxAge(t *testing.T) { currentTime = fakeTime + + // change how maxage is interpreted from days to milliseconds + day = time.Millisecond + // This test uses ModTime on files, and so we need to make sure we're using // the most current time possible. fakeCurrentTime = time.Now() @@ -339,7 +344,7 @@ func TestMaxAge(t *testing.T) { Dir: dir, NameFormat: format, MaxSize: 10, - MaxAge: 10 * time.Millisecond, + MaxAge: 10, } defer l.Close() b := []byte("boo!") @@ -415,7 +420,7 @@ func TestDefaultDirAndName(t *testing.T) { err = l.Close() isNil(err, t) - defer newFakeTime(Day)() + newFakeTime() // even though the old file is under MaxSize, we should write a new file // to prevent two processes using lumberjack from writing to the same file. @@ -452,7 +457,7 @@ func TestRotate(t *testing.T) { fileCount(dir, 1, t) // set the current time one day later - defer newFakeTime(Day)() + newFakeTime() err = l.Rotate() isNil(err, t) @@ -467,7 +472,7 @@ func TestRotate(t *testing.T) { fileCount(dir, 2, t) // set the current time one day later - defer newFakeTime(Day)() + newFakeTime() err = l.Rotate() isNil(err, t) @@ -490,6 +495,28 @@ func TestRotate(t *testing.T) { existsWithLen(filename3, n, t) } +func TestUnmarshal(t *testing.T) { + data := []byte(` +{ + "dir": "foo", + "nameformat": "bar", + "maxsize": 5, + "maxage": 10, + "maxbackups": 3, + "localtime": true +}`[1:]) + + l := Logger{} + err := json.Unmarshal(data, &l) + isNil(err, t) + equals("foo", l.Dir, t) + equals("bar", l.NameFormat, t) + equals(int64(5), l.MaxSize, t) + equals(10, l.MaxAge, t) + equals(3, l.MaxBackups, t) + equals(true, l.LocalTime, t) +} + // makeTempDir creates a file with a semi-unique name in the OS temp directory. // It should be based on the name of the test, to keep parallel tests from // colliding, and must be cleaned up after the test is finished. @@ -528,12 +555,8 @@ func fileCount(dir string, exp int, t testing.TB) { } // newFakeTime sets the fake "current time" to one day later. -func newFakeTime(later time.Duration) func() { - old := fakeCurrentTime - fakeCurrentTime = fakeCurrentTime.Add(later) - return func() { - fakeCurrentTime = old - } +func newFakeTime() { + fakeCurrentTime = fakeCurrentTime.Add(time.Hour * 24) } func notExist(path string, t testing.TB) {