Enable loss less rotation of log files

This commit is contained in:
Marco Jantke 2017-09-15 15:02:03 +02:00 committed by Traefiker
parent 0a0cf87625
commit cf387d5a6d
5 changed files with 175 additions and 5 deletions

View file

@ -227,8 +227,10 @@ func RotateFile() error {
return nil return nil
} }
if err := CloseFile(); err != nil { if logFile != nil {
return fmt.Errorf("error closing log file: %s", err) defer func(f *os.File) {
f.Close()
}(logFile)
} }
if err := OpenFile(logFilePath); err != nil { if err := OpenFile(logFilePath); err != nil {

79
log/logger_test.go Normal file
View file

@ -0,0 +1,79 @@
package log
import (
"io/ioutil"
"os"
"strings"
"testing"
"time"
)
func TestLogRotation(t *testing.T) {
tempDir, err := ioutil.TempDir("", "traefik_")
if err != nil {
t.Fatalf("Error setting up temporary directory: %s", err)
}
fileName := tempDir + "traefik.log"
if err := OpenFile(fileName); err != nil {
t.Fatalf("Error opening temporary file %s: %s", fileName, err)
}
defer CloseFile()
rotatedFileName := fileName + ".rotated"
iterations := 20
halfDone := make(chan bool)
writeDone := make(chan bool)
go func() {
for i := 0; i < iterations; i++ {
Println("Test log line")
if i == iterations/2 {
halfDone <- true
}
}
writeDone <- true
}()
<-halfDone
err = os.Rename(fileName, rotatedFileName)
if err != nil {
t.Fatalf("Error renaming file: %s", err)
}
err = RotateFile()
if err != nil {
t.Fatalf("Error rotating file: %s", err)
}
select {
case <-writeDone:
gotLineCount := lineCount(t, fileName) + lineCount(t, rotatedFileName)
if iterations != gotLineCount {
t.Errorf("Wanted %d written log lines, got %d", iterations, gotLineCount)
}
case <-time.After(500 * time.Millisecond):
t.Fatalf("test timed out")
}
close(halfDone)
close(writeDone)
}
func lineCount(t *testing.T, fileName string) int {
t.Helper()
fileContents, err := ioutil.ReadFile(fileName)
if err != nil {
t.Fatalf("Error reading from file %s: %s", fileName, err)
}
count := 0
for _, line := range strings.Split(string(fileContents), "\n") {
if strings.TrimSpace(line) == "" {
continue
}
count++
}
return count
}

View file

@ -8,6 +8,7 @@ import (
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -34,6 +35,7 @@ type LogHandler struct {
logger *logrus.Logger logger *logrus.Logger
file *os.File file *os.File
filePath string filePath string
mu sync.Mutex
} }
// NewLogHandler creates a new LogHandler // NewLogHandler creates a new LogHandler
@ -148,14 +150,19 @@ func (l *LogHandler) Close() error {
// by an external source. // by an external source.
func (l *LogHandler) Rotate() error { func (l *LogHandler) Rotate() error {
var err error var err error
if err = l.Close(); err != nil {
return err if l.file != nil {
defer func(f *os.File) {
f.Close()
}(l.file)
} }
l.file, err = os.OpenFile(l.filePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0664) l.file, err = os.OpenFile(l.filePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0664)
if err != nil { if err != nil {
return err return err
} }
l.mu.Lock()
defer l.mu.Unlock()
l.logger.Out = l.file l.logger.Out = l.file
return nil return nil
} }
@ -226,6 +233,8 @@ func (l *LogHandler) logTheRoundTrip(logDataTable *LogData, crr *captureRequestR
fields["downstream_"+k] = logDataTable.DownstreamResponse.Get(k) fields["downstream_"+k] = logDataTable.DownstreamResponse.Get(k)
} }
l.mu.Lock()
defer l.mu.Unlock()
l.logger.WithFields(fields).Println() l.logger.WithFields(fields).Println()
} }

View file

@ -10,7 +10,9 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings"
"testing" "testing"
"time"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
shellwords "github.com/mattn/go-shellwords" shellwords "github.com/mattn/go-shellwords"
@ -36,6 +38,84 @@ var (
testRetryAttempts = 2 testRetryAttempts = 2
) )
func TestLogRotation(t *testing.T) {
tempDir, err := ioutil.TempDir("", "traefik_")
if err != nil {
t.Fatalf("Error setting up temporary directory: %s", err)
}
fileName := tempDir + "traefik.log"
rotatedFileName := fileName + ".rotated"
config := &types.AccessLog{FilePath: fileName, Format: CommonFormat}
logHandler, err := NewLogHandler(config)
if err != nil {
t.Fatalf("Error creating new log handler: %s", err)
}
defer logHandler.Close()
recorder := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
next := func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
}
iterations := 20
halfDone := make(chan bool)
writeDone := make(chan bool)
go func() {
for i := 0; i < iterations; i++ {
logHandler.ServeHTTP(recorder, req, next)
if i == iterations/2 {
halfDone <- true
}
}
writeDone <- true
}()
<-halfDone
err = os.Rename(fileName, rotatedFileName)
if err != nil {
t.Fatalf("Error renaming file: %s", err)
}
err = logHandler.Rotate()
if err != nil {
t.Fatalf("Error rotating file: %s", err)
}
select {
case <-writeDone:
gotLineCount := lineCount(t, fileName) + lineCount(t, rotatedFileName)
if iterations != gotLineCount {
t.Errorf("Wanted %d written log lines, got %d", iterations, gotLineCount)
}
case <-time.After(500 * time.Millisecond):
t.Fatalf("test timed out")
}
close(halfDone)
close(writeDone)
}
func lineCount(t *testing.T, fileName string) int {
t.Helper()
fileContents, err := ioutil.ReadFile(fileName)
if err != nil {
t.Fatalf("Error reading from file %s: %s", fileName, err)
}
count := 0
for _, line := range strings.Split(string(fileContents), "\n") {
if strings.TrimSpace(line) == "" {
continue
}
count++
}
return count
}
func TestLoggerCLF(t *testing.T) { func TestLoggerCLF(t *testing.T) {
tmpDir := createTempDir(t, CommonFormat) tmpDir := createTempDir(t, CommonFormat)
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)

View file

@ -27,7 +27,7 @@ func (server *Server) listenSignals() {
} }
if err := log.RotateFile(); err != nil { if err := log.RotateFile(); err != nil {
log.Errorf("Error rotating error log: %s", err) log.Errorf("Error rotating traefik log: %s", err)
} }
default: default:
log.Infof("I have to go... %+v", sig) log.Infof("I have to go... %+v", sig)