add locks and the Rotate() function. Remove IsWriteTooLong function. Fix a bug in the link to time constants.
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
[](https://travis-ci.org/natefinch/lumberjack)
|
[](https://travis-ci.org/natefinch/lumberjack)
|
||||||
|
|
||||||
Lumberjack is a Go package for writing logs to rolling files.
|
### Lumberjack is a Go package for writing logs to rolling files.
|
||||||
|
|
||||||
Lumberjack is intended to be one part of a logging infrastructure.
|
Lumberjack is intended to be one part of a logging infrastructure.
|
||||||
It is not an all-in-one solution, but instead is a pluggable
|
It is not an all-in-one solution, but instead is a pluggable
|
||||||
@@ -25,11 +25,7 @@ into the SetOutput function when your application starts:
|
|||||||
MaxAge: lumberjack.Week * 4,
|
MaxAge: lumberjack.Week * 4,
|
||||||
))
|
))
|
||||||
|
|
||||||
Note that lumberjack assumes whatever is writing to it will use locks to prevent
|
Lumberjack assumes that only one process is writing to the output files.
|
||||||
concurrent writes (the standard library log package already does this).
|
|
||||||
Lumberjack does not implement its own lock.
|
|
||||||
|
|
||||||
Lumberjack also assumes that only one process is writing to the output files.
|
|
||||||
Using the same lumberjack configuration from multiple processes on the same
|
Using the same lumberjack configuration from multiple processes on the same
|
||||||
machine will result in improper behavior.
|
machine will result in improper behavior.
|
||||||
|
|
||||||
@@ -39,63 +35,53 @@ machine will result in improper behavior.
|
|||||||
## Constants
|
## Constants
|
||||||
``` go
|
``` go
|
||||||
const (
|
const (
|
||||||
// Some helper constants to make your declarations easier to read.
|
|
||||||
Megabyte = 1024 * 1024
|
Megabyte = 1024 * 1024
|
||||||
Gigabyte = 1024 * Megabyte
|
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
|
Day = 24 * time.Hour
|
||||||
Week = 7 * Day
|
Week = 7 * Day
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## func IsWriteTooLong
|
|
||||||
``` go
|
|
||||||
func IsWriteTooLong(err error) bool
|
|
||||||
```
|
|
||||||
IsWriteTooLong reports whether the given error indicates a Write with data that
|
|
||||||
exceeds the Logger's MaxSize.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## type Logger
|
## type Logger
|
||||||
``` go
|
``` go
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
// Dir determines the directory in which to store log files.
|
// Dir determines the directory in which to store log files.
|
||||||
// It defaults to os.TempDir() if empty.
|
// It defaults to os.TempDir() if empty.
|
||||||
Dir string
|
Dir string `json:"dir" yaml:"dir"`
|
||||||
|
|
||||||
// NameFormat is the time formatting layout used to generate filenames.
|
// NameFormat is the time formatting layout used to generate filenames.
|
||||||
// It defaults to "2006-01-02T15-04-05.000.log".
|
// It defaults to "2006-01-02T15-04-05.000.log".
|
||||||
NameFormat string
|
NameFormat string `json:"nameformat" yaml:"nameformat"`
|
||||||
|
|
||||||
// MaxSize is the maximum size in bytes of the log file before it gets
|
// MaxSize is the maximum size in bytes of the log file before it gets
|
||||||
// rolled. It defaults to 100 megabytes.
|
// rolled. It defaults to 100 megabytes.
|
||||||
MaxSize int64
|
MaxSize int64 `json:"maxsize" yaml:"maxsize"`
|
||||||
|
|
||||||
// MaxAge is the maximum time to retain old log files based on
|
// 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
|
// FileInfo.ModTime. The default is not to remove old log files based on
|
||||||
// age.
|
// age.
|
||||||
MaxAge time.Duration
|
MaxAge time.Duration `json:"maxage" yaml:"maxage"`
|
||||||
|
|
||||||
// MaxBackups is the maximum number of old log files to retain. The default
|
// 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
|
// is to retain all old log files (though MaxAge may still cause them to get
|
||||||
// deleted.)
|
// deleted.)
|
||||||
MaxBackups int
|
MaxBackups int `json:"maxbackups" yaml:"maxbackups"`
|
||||||
|
|
||||||
// LocalTime determines if the time used for formatting the filename is the
|
// LocalTime determines if the time used for formatting the filename is the
|
||||||
// computer's local time. The default is to use UTC time.
|
// computer's local time. The default is to use UTC time.
|
||||||
LocalTime bool
|
LocalTime bool `json:"localtime" yaml:"localtime"`
|
||||||
|
|
||||||
|
sync.Mutex
|
||||||
// contains filtered or unexported fields
|
// contains filtered or unexported fields
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Logger is an io.WriteCloser that writes to a log file in the given directory
|
Logger is an io.WriteCloser that writes to a log file in the given directory
|
||||||
with the given NameFormat. NameFormat should include a time formatting
|
with the given NameFormat. NameFormat should include a time formatting
|
||||||
layout in it that produces a valid unique filename for the OS. For more
|
layout in it that produces a valid unique filename for the OS. For more
|
||||||
about time formatting layouts, read a href="http://golang.org/pkg/time/#pkg-">http://golang.org/pkg/time/#pkg-</a>
|
about time formatting layouts, read a href="http://golang.org/pkg/time/#pkg-constants">http://golang.org/pkg/time/#pkg-constants</a>.
|
||||||
constants.
|
|
||||||
|
|
||||||
The date encoded in the filename by NameFormat is used to determine which log
|
The date encoded in the filename by NameFormat is used to determine which log
|
||||||
files are most recent in several situations.
|
files are most recent in several situations.
|
||||||
@@ -137,6 +123,18 @@ Close implements io.Closer, and closes the current logfile.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### func (\*Logger) Rotate
|
||||||
|
``` go
|
||||||
|
func (l *Logger) Rotate() error
|
||||||
|
```
|
||||||
|
Rotate causes Logger to close the existing log file and immediately create a
|
||||||
|
new one. This is a helper function for applications that want to initiate
|
||||||
|
rotations outside of the normal rotation rules, such as in response to
|
||||||
|
SIGHUP. After rotating, this initiates a cleanup of old log files according
|
||||||
|
to the normal rules.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### func (\*Logger) Write
|
### func (\*Logger) Write
|
||||||
``` go
|
``` go
|
||||||
func (l *Logger) Write(p []byte) (n int, err error)
|
func (l *Logger) Write(p []byte) (n int, err error)
|
||||||
|
|||||||
+48
-38
@@ -19,10 +19,7 @@
|
|||||||
// MaxAge: lumberjack.Week * 4,
|
// MaxAge: lumberjack.Week * 4,
|
||||||
// ))
|
// ))
|
||||||
//
|
//
|
||||||
// Note that lumberjack assumes whatever is writing to it will use locks to
|
// Lumberjack assumes that only one process is writing to the output files.
|
||||||
// prevent concurrent writes. Lumberjack does not implement its own lock.
|
|
||||||
//
|
|
||||||
// Lumberjack also assumes that only one process is writing to the output files.
|
|
||||||
// Using the same lumberjack configuration from multiple processes on the same
|
// Using the same lumberjack configuration from multiple processes on the same
|
||||||
// machine will result in improper behavior.
|
// machine will result in improper behavior.
|
||||||
package lumberjack
|
package lumberjack
|
||||||
@@ -34,6 +31,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -59,8 +57,7 @@ var _ io.WriteCloser = &Logger{}
|
|||||||
// Logger is an io.WriteCloser that writes to a log file in the given directory
|
// Logger is an io.WriteCloser that writes to a log file in the given directory
|
||||||
// with the given NameFormat. NameFormat should include a time formatting
|
// with the given NameFormat. NameFormat should include a time formatting
|
||||||
// layout in it that produces a valid unique filename for the OS. For more
|
// layout in it that produces a valid unique filename for the OS. For more
|
||||||
// about time formatting layouts, read http://golang.org/pkg/time/#pkg-
|
// about time formatting layouts, read http://golang.org/pkg/time/#pkg-constants.
|
||||||
// constants.
|
|
||||||
//
|
//
|
||||||
// The date encoded in the filename by NameFormat is used to determine which log
|
// The date encoded in the filename by NameFormat is used to determine which log
|
||||||
// files are most recent in several situations.
|
// files are most recent in several situations.
|
||||||
@@ -87,32 +84,33 @@ var _ io.WriteCloser = &Logger{}
|
|||||||
type Logger struct {
|
type Logger struct {
|
||||||
// Dir determines the directory in which to store log files.
|
// Dir determines the directory in which to store log files.
|
||||||
// It defaults to os.TempDir() if empty.
|
// It defaults to os.TempDir() if empty.
|
||||||
Dir string
|
Dir string `json:"dir" yaml:"dir"`
|
||||||
|
|
||||||
// NameFormat is the time formatting layout used to generate filenames.
|
// NameFormat is the time formatting layout used to generate filenames.
|
||||||
// It defaults to "2006-01-02T15-04-05.000.log".
|
// It defaults to "2006-01-02T15-04-05.000.log".
|
||||||
NameFormat string
|
NameFormat string `json:"nameformat" yaml:"nameformat"`
|
||||||
|
|
||||||
// MaxSize is the maximum size in bytes of the log file before it gets
|
// MaxSize is the maximum size in bytes of the log file before it gets
|
||||||
// rolled. It defaults to 100 megabytes.
|
// rolled. It defaults to 100 megabytes.
|
||||||
MaxSize int64
|
MaxSize int64 `json:"maxsize" yaml:"maxsize"`
|
||||||
|
|
||||||
// MaxAge is the maximum time to retain old log files based on
|
// 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
|
// FileInfo.ModTime. The default is not to remove old log files based on
|
||||||
// age.
|
// age.
|
||||||
MaxAge time.Duration
|
MaxAge time.Duration `json:"maxage" yaml:"maxage"`
|
||||||
|
|
||||||
// MaxBackups is the maximum number of old log files to retain. The default
|
// 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
|
// is to retain all old log files (though MaxAge may still cause them to get
|
||||||
// deleted.)
|
// deleted.)
|
||||||
MaxBackups int
|
MaxBackups int `json:"maxbackups" yaml:"maxbackups"`
|
||||||
|
|
||||||
// LocalTime determines if the time used for formatting the filename is the
|
// LocalTime determines if the time used for formatting the filename is the
|
||||||
// computer's local time. The default is to use UTC time.
|
// computer's local time. The default is to use UTC time.
|
||||||
LocalTime bool
|
LocalTime bool `json:"localtime" yaml:"localtime"`
|
||||||
|
|
||||||
size int64
|
size int64
|
||||||
file *os.File
|
file *os.File
|
||||||
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// currentTime is only used for testing. Normally it's the time.Now() function.
|
// currentTime is only used for testing. Normally it's the time.Now() function.
|
||||||
@@ -123,11 +121,13 @@ var currentTime = time.Now
|
|||||||
// PathFormat. If the length of the write is greater than MaxSize, an error is
|
// PathFormat. If the length of the write is greater than MaxSize, an error is
|
||||||
// returned that satisfies IsWriteTooLong.
|
// returned that satisfies IsWriteTooLong.
|
||||||
func (l *Logger) Write(p []byte) (n int, err error) {
|
func (l *Logger) Write(p []byte) (n int, err error) {
|
||||||
|
l.Lock()
|
||||||
|
defer l.Unlock()
|
||||||
writeLen := int64(len(p))
|
writeLen := int64(len(p))
|
||||||
if writeLen > l.max() {
|
if writeLen > l.max() {
|
||||||
return 0, writeTooLongError{fmt.Errorf(
|
return 0, fmt.Errorf(
|
||||||
"write length %d exceeds maximum file size %d", writeLen, l.max(),
|
"write length %d exceeds maximum file size %d", writeLen, l.max(),
|
||||||
)}
|
)
|
||||||
}
|
}
|
||||||
f := l.file
|
f := l.file
|
||||||
rotate := l.size+writeLen > l.max()
|
rotate := l.size+writeLen > l.max()
|
||||||
@@ -158,6 +158,40 @@ func (l *Logger) Write(p []byte) (n int, err error) {
|
|||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close implements io.Closer, and closes the current logfile.
|
||||||
|
func (l *Logger) Close() error {
|
||||||
|
l.Lock()
|
||||||
|
defer l.Unlock()
|
||||||
|
if l.file != nil {
|
||||||
|
err := l.file.Close()
|
||||||
|
l.file = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate causes Logger to close the existing log file and immediately create a
|
||||||
|
// new one. This is a helper function for applications that want to initiate
|
||||||
|
// rotations outside of the normal rotation rules, such as in response to
|
||||||
|
// SIGHUP. After rotating, this initiates a cleanup of old log files according
|
||||||
|
// to the normal rules.
|
||||||
|
func (l *Logger) Rotate() error {
|
||||||
|
l.Lock()
|
||||||
|
defer l.Unlock()
|
||||||
|
if l.file != nil {
|
||||||
|
if err := l.file.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.file = nil
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
l.file, err = l.openNew()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return l.cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
// openNew opens a new log file for writing.
|
// openNew opens a new log file for writing.
|
||||||
func (l *Logger) openNew() (*os.File, error) {
|
func (l *Logger) openNew() (*os.File, error) {
|
||||||
err := os.MkdirAll(l.dir(), 0744)
|
err := os.MkdirAll(l.dir(), 0744)
|
||||||
@@ -306,16 +340,6 @@ func (l *Logger) isLogFile(f os.FileInfo) bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close implements io.Closer, and closes the current logfile.
|
|
||||||
func (l *Logger) Close() error {
|
|
||||||
if l.file != nil {
|
|
||||||
err := l.file.Close()
|
|
||||||
l.file = nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Logger) max() int64 {
|
func (l *Logger) max() int64 {
|
||||||
if l.MaxSize == 0 {
|
if l.MaxSize == 0 {
|
||||||
return defaultMaxSize
|
return defaultMaxSize
|
||||||
@@ -362,17 +386,3 @@ func (b byFormatTime) time(i int) time.Time {
|
|||||||
}
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsWriteTooLong reports whether the given error indicates a Write with data
|
|
||||||
// that exceeds the Logger's MaxSize.
|
|
||||||
func IsWriteTooLong(err error) bool {
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_, ok := err.(writeTooLongError)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
type writeTooLongError struct {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
|
|||||||
+66
-12
@@ -1,7 +1,7 @@
|
|||||||
package lumberjack
|
package lumberjack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -85,18 +85,12 @@ func TestWriteTooLong(t *testing.T) {
|
|||||||
defer l.Close()
|
defer l.Close()
|
||||||
b := []byte("booooooooooooooo!")
|
b := []byte("booooooooooooooo!")
|
||||||
n, err := l.Write(b)
|
n, err := l.Write(b)
|
||||||
assert(IsWriteTooLong(err), t,
|
notNil(err, t)
|
||||||
"Should have gotten write too long error, instead got %s (%T)", err, err)
|
|
||||||
equals(0, n, t)
|
equals(0, n, t)
|
||||||
|
equals(err.Error(),
|
||||||
|
fmt.Sprintf("write length %d exceeds maximum file size %d", len(b), l.MaxSize), t)
|
||||||
_, err = os.Stat(logFile(dir))
|
_, err = os.Stat(logFile(dir))
|
||||||
assert(os.IsNotExist(err), t, "File exists, but should not have been created")
|
assert(os.IsNotExist(err), t, "File exists, but should not have been created")
|
||||||
|
|
||||||
newerr := errors.New("foo")
|
|
||||||
|
|
||||||
assert(!IsWriteTooLong(nil), t,
|
|
||||||
"Nil error should not return true for IsWriteTooLong, but did.")
|
|
||||||
assert(!IsWriteTooLong(newerr), t,
|
|
||||||
"Different error should not return true for IsWriteTooLong, but did.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMakeLogDir(t *testing.T) {
|
func TestMakeLogDir(t *testing.T) {
|
||||||
@@ -151,9 +145,9 @@ func TestDefaultFilename(t *testing.T) {
|
|||||||
fileCount(dir, 1, t)
|
fileCount(dir, 1, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRotate(t *testing.T) {
|
func TestAutoRotate(t *testing.T) {
|
||||||
currentTime = fakeTime
|
currentTime = fakeTime
|
||||||
dir := makeTempDir("TestRotate", t)
|
dir := makeTempDir("TestAutoRotate", t)
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
l := &Logger{
|
l := &Logger{
|
||||||
@@ -405,6 +399,66 @@ func TestDefaultDirAndName(t *testing.T) {
|
|||||||
existsWithLen(f2, n, t)
|
existsWithLen(f2, n, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRotate(t *testing.T) {
|
||||||
|
currentTime = fakeTime
|
||||||
|
dir := makeTempDir("TestRotate", t)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
l := &Logger{
|
||||||
|
Dir: dir,
|
||||||
|
NameFormat: format,
|
||||||
|
MaxBackups: 1,
|
||||||
|
MaxSize: Megabyte,
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
b := []byte("boo!")
|
||||||
|
n, err := l.Write(b)
|
||||||
|
isNil(err, t)
|
||||||
|
equals(len(b), n, t)
|
||||||
|
|
||||||
|
filename := logFile(dir)
|
||||||
|
existsWithLen(filename, n, t)
|
||||||
|
fileCount(dir, 1, t)
|
||||||
|
|
||||||
|
// set the current time one day later
|
||||||
|
defer newFakeTime(Day)()
|
||||||
|
|
||||||
|
err = l.Rotate()
|
||||||
|
isNil(err, t)
|
||||||
|
|
||||||
|
// we need to wait a little bit since the files get deleted on a different
|
||||||
|
// goroutine.
|
||||||
|
<-time.After(10 * time.Millisecond)
|
||||||
|
|
||||||
|
filename2 := logFile(dir)
|
||||||
|
existsWithLen(filename2, 0, t)
|
||||||
|
existsWithLen(filename, n, t)
|
||||||
|
fileCount(dir, 2, t)
|
||||||
|
|
||||||
|
// set the current time one day later
|
||||||
|
defer newFakeTime(Day)()
|
||||||
|
|
||||||
|
err = l.Rotate()
|
||||||
|
isNil(err, t)
|
||||||
|
|
||||||
|
// we need to wait a little bit since the files get deleted on a different
|
||||||
|
// goroutine.
|
||||||
|
<-time.After(10 * time.Millisecond)
|
||||||
|
|
||||||
|
filename3 := logFile(dir)
|
||||||
|
existsWithLen(filename3, 0, t)
|
||||||
|
existsWithLen(filename2, 0, t)
|
||||||
|
fileCount(dir, 2, t)
|
||||||
|
|
||||||
|
b2 := []byte("foooooo!")
|
||||||
|
n, err = l.Write(b2)
|
||||||
|
isNil(err, t)
|
||||||
|
equals(len(b2), n, t)
|
||||||
|
|
||||||
|
// this will use the new fake time
|
||||||
|
existsWithLen(filename3, n, t)
|
||||||
|
}
|
||||||
|
|
||||||
// makeTempDir creates a file with a semi-unique name in the OS temp directory.
|
// 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
|
// 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.
|
// colliding, and must be cleaned up after the test is finished.
|
||||||
|
|||||||
Reference in New Issue
Block a user