From 82beb2c5f38b168db83c18b9e3c7c3948a2776f1 Mon Sep 17 00:00:00 2001 From: Eric <45141191+zlacfzy@users.noreply.github.com> Date: Tue, 30 Jan 2024 19:16:01 +0800 Subject: [PATCH] log: support maxBackups in config.toml (#2186) --- log/async_file_writer.go | 46 ++++++++++++++++++++++++++++++----- log/async_file_writer_test.go | 23 +++++++++++++++++- log/handler.go | 4 +-- log/logger.go | 4 +-- node/config.go | 1 + node/node.go | 7 +++++- 6 files changed, 73 insertions(+), 12 deletions(-) diff --git a/log/async_file_writer.go b/log/async_file_writer.go index b4e7a60f30..7bfc669c22 100644 --- a/log/async_file_writer.go +++ b/log/async_file_writer.go @@ -10,6 +10,8 @@ import ( "time" ) +const backupTimeFormat = "2006-01-02_15" + type TimeTicker struct { stop chan struct{} C <-chan time.Time @@ -69,19 +71,24 @@ type AsyncFileWriter struct { buf chan []byte stop chan struct{} timeTicker *TimeTicker + + rotateHours uint + maxBackups int } -func NewAsyncFileWriter(filePath string, maxBytesSize int64, rotateHours uint) *AsyncFileWriter { +func NewAsyncFileWriter(filePath string, maxBytesSize int64, maxBackups int, rotateHours uint) *AsyncFileWriter { absFilePath, err := filepath.Abs(filePath) if err != nil { panic(fmt.Sprintf("get file path of logger error. filePath=%s, err=%s", filePath, err)) } return &AsyncFileWriter{ - filePath: absFilePath, - buf: make(chan []byte, maxBytesSize), - stop: make(chan struct{}), - timeTicker: NewTimeTicker(rotateHours), + filePath: absFilePath, + buf: make(chan []byte, maxBytesSize), + stop: make(chan struct{}), + rotateHours: rotateHours, + maxBackups: maxBackups, + timeTicker: NewTimeTicker(rotateHours), } } @@ -178,6 +185,9 @@ func (w *AsyncFileWriter) rotateFile() { if err := w.initLogFile(); err != nil { fmt.Fprintf(os.Stderr, "init log file error. err=%s", err) } + if err := w.removeExpiredFile(); err != nil { + fmt.Fprintf(os.Stderr, "remove expired file error. err=%s", err) + } default: } } @@ -222,5 +232,29 @@ func (w *AsyncFileWriter) flushAndClose() error { } func (w *AsyncFileWriter) timeFilePath(filePath string) string { - return filePath + "." + time.Now().Format("2006-01-02_15") + return filePath + "." + time.Now().Format(backupTimeFormat) +} + +func (w *AsyncFileWriter) getExpiredFile(filePath string, maxBackups int, rotateHours uint) string { + if rotateHours > 0 { + maxBackups = int(rotateHours) * maxBackups + } + return filePath + "." + time.Now().Add(-time.Hour*time.Duration(maxBackups)).Format(backupTimeFormat) +} + +func (w *AsyncFileWriter) removeExpiredFile() error { + if w.maxBackups == 0 { + return nil + } + + oldFilepath := w.getExpiredFile(w.filePath, w.maxBackups, w.rotateHours) + _, err := os.Stat(oldFilepath) + if os.IsNotExist(err) { + return nil + } + errRemove := os.Remove(oldFilepath) + if err != nil { + return errRemove + } + return err } diff --git a/log/async_file_writer_test.go b/log/async_file_writer_test.go index ab12808856..ddbfbdb668 100644 --- a/log/async_file_writer_test.go +++ b/log/async_file_writer_test.go @@ -6,10 +6,12 @@ import ( "strings" "testing" "time" + + "github.com/stretchr/testify/assert" ) func TestWriterHourly(t *testing.T) { - w := NewAsyncFileWriter("./hello.log", 100, 1) + w := NewAsyncFileWriter("./hello.log", 100, 1, 1) w.Start() w.Write([]byte("hello\n")) w.Write([]byte("world\n")) @@ -67,3 +69,22 @@ func TestGetNextRotationHour(t *testing.T) { t.Run("TestGetNextRotationHour_"+strconv.Itoa(i), test(tc.now, tc.delta, tc.expectedHour)) } } + +func TestClearBackups(t *testing.T) { + dir := "./test" + os.Mkdir(dir, 0700) + w := NewAsyncFileWriter("./test/bsc.log", 100, 1, 1) + defer os.RemoveAll(dir) + fakeCurrentTime := time.Now() + name := "" + data := []byte("data") + for i := 0; i < 5; i++ { + name = w.filePath + "." + fakeCurrentTime.Format(backupTimeFormat) + _ = os.WriteFile(name, data, 0700) + fakeCurrentTime = fakeCurrentTime.Add(-time.Hour * 1) + } + oldFile := w.getExpiredFile(w.filePath, w.maxBackups, w.rotateHours) + w.removeExpiredFile() + _, err := os.Stat(oldFile) + assert.True(t, os.IsNotExist(err)) +} diff --git a/log/handler.go b/log/handler.go index bc407857f6..c4b0e9bdf9 100644 --- a/log/handler.go +++ b/log/handler.go @@ -75,14 +75,14 @@ func FileHandler(path string, fmtr Format) (Handler, error) { // RotatingFileHandler returns a handler which writes log records to file chunks // at the given path. When a file's size reaches the limit, the handler creates // a new file named after the timestamp of the first log record it will contain. -func RotatingFileHandler(filePath string, limit uint, formatter Format, rotateHours uint) (Handler, error) { +func RotatingFileHandler(filePath string, limit uint, maxBackups uint, formatter Format, rotateHours uint) (Handler, error) { if _, err := os.Stat(path.Dir(filePath)); os.IsNotExist(err) { err := os.MkdirAll(path.Dir(filePath), 0755) if err != nil { return nil, fmt.Errorf("could not create directory %s, %v", path.Dir(filePath), err) } } - fileWriter := NewAsyncFileWriter(filePath, int64(limit), rotateHours) + fileWriter := NewAsyncFileWriter(filePath, int64(limit), int(maxBackups), rotateHours) fileWriter.Start() return StreamHandler(fileWriter, formatter), nil } diff --git a/log/logger.go b/log/logger.go index 5b89e699ec..3223742ea7 100644 --- a/log/logger.go +++ b/log/logger.go @@ -290,8 +290,8 @@ func (c Ctx) toArray() []interface{} { return arr } -func NewFileLvlHandler(logPath string, maxBytesSize uint, level string, rotateHours uint) Handler { - rfh, err := RotatingFileHandler(logPath, maxBytesSize, LogfmtFormat(), rotateHours) +func NewFileLvlHandler(logPath string, maxBytesSize uint, maxBackups uint, level string, rotateHours uint) Handler { + rfh, err := RotatingFileHandler(logPath, maxBytesSize, maxBackups, LogfmtFormat(), rotateHours) if err != nil { panic(err) } diff --git a/node/config.go b/node/config.go index dc27d48a58..bc30a0ab0a 100644 --- a/node/config.go +++ b/node/config.go @@ -513,6 +513,7 @@ type LogConfig struct { MaxBytesSize *uint `toml:",omitempty"` Level *string `toml:",omitempty"` RotateHours *uint `toml:",omitempty"` + MaxBackups *uint `toml:",omitempty"` // TermTimeFormat is the time format used for console logging. TermTimeFormat *string `toml:",omitempty"` diff --git a/node/node.go b/node/node.go index 37be56b0ad..7f8872f9cf 100644 --- a/node/node.go +++ b/node/node.go @@ -118,7 +118,12 @@ func New(conf *Config) (*Node, error) { rotateHours = *conf.LogConfig.RotateHours } - log.Root().SetHandler(log.NewFileLvlHandler(logFilePath, *conf.LogConfig.MaxBytesSize, *conf.LogConfig.Level, rotateHours)) + maxBackups := uint(0) + if conf.LogConfig.MaxBackups != nil { + maxBackups = *conf.LogConfig.MaxBackups + } + + log.Root().SetHandler(log.NewFileLvlHandler(logFilePath, *conf.LogConfig.MaxBytesSize, maxBackups, *conf.LogConfig.Level, rotateHours)) } } if conf.Logger == nil {