Skip to content

Commit

Permalink
Do not compress latest log file
Browse files Browse the repository at this point in the history
  • Loading branch information
ttetzlaff authored and yinonavraham committed Feb 4, 2021
1 parent 8511fb9 commit d2251bd
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 71 deletions.
16 changes: 14 additions & 2 deletions lumberjack.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ type Logger struct {
// using gzip. The default is not to perform compression.
Compress bool `json:"compress" yaml:"compress"`

// KeepLastDecompressed determines the number of rotated logs to keep decompressed.
// This is only used if Compress is true. The default (0) is to compress all rotated logs.
KeepLastDecompressed int `json:"keeplastdecompressed" yaml:"keeplastdecompressed"`

// TimeFormat determines the format to use for formatting the timestamp in
// backup files. The default format is defined in `DefaultTimeFormat`.
TimeFormat string `json:"timeformat" yaml:"timeformat"`
Expand Down Expand Up @@ -379,8 +383,8 @@ func (l *Logger) millRunOnce() error {
}

if l.Compress {
for _, f := range files {
if !strings.HasSuffix(f.Name(), compressSuffix) {
for i, f := range files {
if shouldCompressFile(l.KeepLastDecompressed, i, f.Name()) {
compress = append(compress, f)
}
}
Expand All @@ -403,6 +407,14 @@ func (l *Logger) millRunOnce() error {
return err
}

func shouldCompressFile(keepLastDecompressed int, fileIndex int, filename string) bool {
alreadyCompressed := strings.HasSuffix(filename, compressSuffix)
if alreadyCompressed || fileIndex < keepLastDecompressed {
return false
}
return true
}

// millRun runs in a goroutine to manage post-rotation compression and removal
// of old log files.
func (l *Logger) millRun() {
Expand Down
259 changes: 190 additions & 69 deletions lumberjack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -631,101 +631,167 @@ func TestRotate(t *testing.T) {
}

func TestCompressOnRotate(t *testing.T) {
currentTime = fakeTime
megabyte = 1
tests := []struct {
name string
keepLastDecompressed int
verifyFirst func(string, []byte, testing.TB)
verifySecond func(string, []byte, testing.TB)
}{
{
name: "compress all",
keepLastDecompressed: 0,
verifyFirst: verifyCompressedFile,
verifySecond: verifyCompressedFile,
},
{
name: "keep 1 decompressed",
keepLastDecompressed: 1,
verifyFirst: verifyCompressedFile,
verifySecond: existsWithContent,
},
{
name: "keep 2 decompressed",
keepLastDecompressed: 2,
verifyFirst: existsWithContent,
verifySecond: existsWithContent,
},
}

dir := makeTempDir("TestCompressOnRotate", t)
defer os.RemoveAll(dir)
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
currentTime = fakeTime
megabyte = 1

dir := makeTempDir("TestCompressOnRotate", t)
defer func() { _ = os.RemoveAll(dir) }()

logFilename := logFile(dir)
l := &Logger{
Compress: true,
KeepLastDecompressed: test.keepLastDecompressed,
Filename: logFilename,
MaxSize: 10,
}
defer l.Close()
booBytes := []byte("boo!")
writeToCurrentLog(t, l, logFilename, booBytes)

filename := logFile(dir)
l := &Logger{
Compress: true,
Filename: filename,
MaxSize: 10,
}
defer l.Close()
b := []byte("boo!")
n, err := l.Write(b)
isNil(err, t)
equals(len(b), n, t)
fileCount(dir, 1, t)

existsWithContent(filename, b, t)
fileCount(dir, 1, t)
newFakeTime()
firstArchiveTime := fakeTime()

newFakeTime()
err := l.Rotate()
isNil(err, t)

err = l.Rotate()
isNil(err, t)
// the old logfile should be moved aside and the main logfile should have
// nothing in it.
oldLogFilename := backupFileWithTime(dir, firstArchiveTime)
existsWithContent(oldLogFilename, booBytes, t)
existsWithContent(logFilename, []byte{}, t)

// the old logfile should be moved aside and the main logfile should have
// nothing in it.
existsWithContent(filename, []byte{}, t)
haaBytes := []byte("haaa!")
writeToCurrentLog(t, l, logFilename, haaBytes)

// we need to wait a little bit since the files get compressed on a different
// goroutine.
<-time.After(300 * time.Millisecond)
newFakeTime()
secondArchiveTime := fakeTime()

// a compressed version of the log file should now exist and the original
// should have been removed.
bc := new(bytes.Buffer)
gz := gzip.NewWriter(bc)
_, err = gz.Write(b)
isNil(err, t)
err = gz.Close()
isNil(err, t)
existsWithContent(backupFile(dir)+compressSuffix, bc.Bytes(), t)
notExist(backupFile(dir), t)
err = l.Rotate()
isNil(err, t)
// we need to wait a little bit since the files get compressed on a different
// goroutine.
<-time.After(300 * time.Millisecond)

fileCount(dir, 2, t)
test.verifyFirst(backupFileWithTime(dir, firstArchiveTime), booBytes, t)
test.verifySecond(backupFileWithTime(dir, secondArchiveTime), haaBytes, t)

fileCount(dir, 3, t)
})
}
}

func TestCompressOnResume(t *testing.T) {
currentTime = fakeTime
megabyte = 1
tests := []struct {
name string
keepLastDecompressed int
expectedFileCount int
}{
{
name: "compress latest",
keepLastDecompressed: 0,
expectedFileCount: 2,
},
{
name: "don't compress latest",
keepLastDecompressed: 1,
expectedFileCount: 3,
},
}

dir := makeTempDir("TestCompressOnResume", t)
defer os.RemoveAll(dir)
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
currentTime = fakeTime
megabyte = 1

dir := makeTempDir("TestCompressOnResume", t)
defer os.RemoveAll(dir)

filename := logFile(dir)
l := &Logger{
Compress: true,
KeepLastDecompressed: test.keepLastDecompressed,
Filename: filename,
MaxSize: 10,
}
defer l.Close()

filename := logFile(dir)
l := &Logger{
Compress: true,
Filename: filename,
MaxSize: 10,
}
defer l.Close()
t1 := fakeTime()
// Create a backup file and empty "compressed" file.
previouslyArchivedFile := backupFileWithTime(dir, t1)
fooBytes := []byte("foo!")
err := ioutil.WriteFile(previouslyArchivedFile, fooBytes, 0644)
isNil(err, t)
err = ioutil.WriteFile(previouslyArchivedFile+compressSuffix, []byte{}, 0644)
isNil(err, t)

// Create a backup file and empty "compressed" file.
filename2 := backupFile(dir)
b := []byte("foo!")
err := ioutil.WriteFile(filename2, b, 0644)
isNil(err, t)
err = ioutil.WriteFile(filename2+compressSuffix, []byte{}, 0644)
isNil(err, t)
writeToCurrentLog(t, l, filename, []byte("boo!"))
newFakeTime()

newFakeTime()
if test.keepLastDecompressed > 0 {
// in this case another backup file is needed
writeToCurrentLog(t, l, filename, []byte("haaaaa!"))
newFakeTime()
}

b2 := []byte("boo!")
n, err := l.Write(b2)
isNil(err, t)
equals(len(b2), n, t)
existsWithContent(filename, b2, t)
// we need to wait a little bit since the files get compressed on a different
// goroutine.
<-time.After(300 * time.Millisecond)

// we need to wait a little bit since the files get compressed on a different
// goroutine.
<-time.After(300 * time.Millisecond)
verifyCompressedFile(previouslyArchivedFile, fooBytes, t)
fileCount(dir, test.expectedFileCount, t)
})
}
}

func verifyCompressedFile(archivedFilename string, contents []byte, t testing.TB) {
// The write should have started the compression - a compressed version of
// the log file should now exist and the original should have been removed.
bc := new(bytes.Buffer)
gz := gzip.NewWriter(bc)
_, err = gz.Write(b)
_, err := gz.Write(contents)
isNil(err, t)
err = gz.Close()
isNil(err, t)
existsWithContent(filename2+compressSuffix, bc.Bytes(), t)
notExist(filename2, t)
existsWithContent(archivedFilename+compressSuffix, bc.Bytes(), t)
notExist(archivedFilename, t)
}

fileCount(dir, 2, t)
func writeToCurrentLog(t *testing.T, l *Logger, filename string, contents []byte) {
b := contents
n, err := l.Write(b)
isNil(err, t)
equals(len(b), n, t)
existsWithContent(filename, b, t)
}

func TestJson(t *testing.T) {
Expand All @@ -737,6 +803,7 @@ func TestJson(t *testing.T) {
"maxbackups": 3,
"localtime": true,
"compress": true,
"keeplastdecompressed": 2,
"timeformat": "1:2.3",
"backupdir": "bar"
}`[1:])
Expand All @@ -750,6 +817,7 @@ func TestJson(t *testing.T) {
equals(3, l.MaxBackups, t)
equals(true, l.LocalTime, t)
equals(true, l.Compress, t)
equals(2, l.KeepLastDecompressed, t)
equals("1:2.3", l.TimeFormat, t)
equals("bar", l.BackupDir, t)
}
Expand All @@ -762,6 +830,7 @@ maxage: 10
maxbackups: 3
localtime: true
compress: true
keeplastdecompressed: 2
timeformat: 1:2.3
backupdir: bar`[1:])

Expand All @@ -774,6 +843,7 @@ backupdir: bar`[1:])
equals(3, l.MaxBackups, t)
equals(true, l.LocalTime, t)
equals(true, l.Compress, t)
equals(2, l.KeepLastDecompressed, t)
equals("1:2.3", l.TimeFormat, t)
equals("bar", l.BackupDir, t)
}
Expand All @@ -786,6 +856,7 @@ maxage = 10
maxbackups = 3
localtime = true
compress = true
keeplastdecompressed = 2
timeformat = "1:2.3"
backupdir = "bar"`[1:]

Expand All @@ -798,11 +869,58 @@ backupdir = "bar"`[1:]
equals(3, l.MaxBackups, t)
equals(true, l.LocalTime, t)
equals(true, l.Compress, t)
equals(2, l.KeepLastDecompressed, t)
equals("1:2.3", l.TimeFormat, t)
equals("bar", l.BackupDir, t)
equals(0, len(md.Undecoded()), t)
}

func TestShouldCompressFile(t *testing.T) {
tests := []struct {
name string
keepLastDecompressed int
filename string
fileIndices []int
expected []bool
}{
{
name: "compress all",
filename: "foo.log",
fileIndices: []int{0, 1, 2, 3},
keepLastDecompressed: 0,
expected: []bool{true, true, true, true},
},
{
name: "leave 2 decompressed",
filename: "foo.log",
fileIndices: []int{0, 1, 2, 3},
keepLastDecompressed: 2,
expected: []bool{false, false, true, true},
},
{
name: "leave 5 decompressed",
filename: "foo.log",
fileIndices: []int{0, 1, 2, 3},
keepLastDecompressed: 5,
expected: []bool{false, false, false, false},
},
{
name: "file already compressed",
filename: "foo.log.gz",
fileIndices: []int{0, 1, 2, 3},
keepLastDecompressed: 0,
expected: []bool{false, false, false, false},
},
}

for _, test := range tests {
for _, i := range test.fileIndices {
equals(test.expected[i], shouldCompressFile(test.keepLastDecompressed, i, test.filename), t)
}
}

}

func forEachBackupTestSpec(t *testing.T, do func(t *testing.T, test backupTestSpec)) {
for _, test := range backupTestSpecs() {
t.Run(test.name, func(t *testing.T) {
Expand Down Expand Up @@ -875,14 +993,17 @@ func logFile(dir string) string {
}

func backupFile(dir string, opts ...backupFileOpt) string {
return backupFileWithTime(dir, fakeTime(), opts...)
}

func backupFileWithTime(dir string, currTime time.Time, opts ...backupFileOpt) string {
options := backupFileOpts{
local: false,
timeFormat: DefaultTimeFormat,
}
for _, opt := range opts {
opt(&options)
}
currTime := fakeTime()
if !options.local {
currTime = currTime.UTC()
}
Expand Down

0 comments on commit d2251bd

Please sign in to comment.