Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

log: support maxBackups in config.toml #2186

Merged
merged 3 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 102 additions & 2 deletions log/async_file_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ package log
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"sync/atomic"
"time"
)

const backupTimeFormat = "2006-01-02_15"

type TimeTicker struct {
stop chan struct{}
C <-chan time.Time
Expand Down Expand Up @@ -69,9 +74,11 @@ type AsyncFileWriter struct {
buf chan []byte
stop chan struct{}
timeTicker *TimeTicker

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))
Expand All @@ -81,6 +88,7 @@ func NewAsyncFileWriter(filePath string, maxBytesSize int64, rotateHours uint) *
filePath: absFilePath,
buf: make(chan []byte, maxBytesSize),
stop: make(chan struct{}),
maxBackups: maxBackups,
timeTicker: NewTimeTicker(rotateHours),
}
}
Expand Down Expand Up @@ -111,6 +119,8 @@ func (w *AsyncFileWriter) initLogFile() error {
return err
}

_ = w.clearBackups()
zzzckck marked this conversation as resolved.
Show resolved Hide resolved

return nil
}

zzzckck marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -222,5 +232,95 @@ 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) dir() string {
return filepath.Dir(w.filePath)
}

// oldLogFiles returns the list of backup log files stored in the same
// directory as the current log file, sorted by ModTime
func (w *AsyncFileWriter) oldLogFiles() ([]logInfo, error) {
files, err := os.ReadDir(w.dir())
if err != nil {
return nil, fmt.Errorf("can't read log file directory: %s", err)
}
logFiles := []logInfo{}

prefix := filepath.Base(w.filePath)

for _, f := range files {
if f.IsDir() {
continue
}
k := f.Name()
if t, err := w.timeFromName(k, prefix); err == nil {
logFiles = append(logFiles, logInfo{t, f})
}
}

sort.Sort(byFormatTime(logFiles))

return logFiles, nil
}

// logInfo is a convenience struct to return the filename and its embedded
// timestamp.
type logInfo struct {
timestamp time.Time
fs.DirEntry
}

// byFormatTime sorts by newest time formatted in the name.
type byFormatTime []logInfo

func (b byFormatTime) Less(i, j int) bool {
return b[i].timestamp.After(b[j].timestamp)
}

func (b byFormatTime) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}

func (b byFormatTime) Len() int {
return len(b)
}

func (w *AsyncFileWriter) timeFromName(filename, prefix string) (time.Time, error) {
if !strings.HasPrefix(filename, prefix) || len(filename) == len(prefix) {
return time.Time{}, errors.New("mismatched prefix")
}
ts := filename[len(prefix)+1:]
a, _ := time.Parse(backupTimeFormat, ts)
return a, nil
}

func (w *AsyncFileWriter) clearBackups() error {
if w.maxBackups == 0 {
return nil
}
files, err := w.oldLogFiles()
if err != nil {
return err
}
var remove []logInfo
if w.maxBackups > 0 && w.maxBackups < len(files) {
preserved := make(map[string]bool)
for _, f := range files {
fn := f.Name()
preserved[fn] = true

if len(preserved) > w.maxBackups {
remove = append(remove, f)
}
}
}
for _, f := range remove {
errRemove := os.Remove(filepath.Join(w.dir(), f.Name()))
if err == nil && errRemove != nil {
err = errRemove
}
}
return err
}
26 changes: 25 additions & 1 deletion log/async_file_writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ package log

import (
"os"
"path/filepath"
"strconv"
"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"))
Expand Down Expand Up @@ -67,3 +70,24 @@ 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)
}
oldFiles, _ := w.oldLogFiles()
assert.True(t, len(oldFiles) == 5)
w.clearBackups()
remainFiles, _ := w.oldLogFiles()
assert.True(t, len(remainFiles) == 1)
assert.Equal(t, remainFiles[0].Name(), filepath.Base(name))
}
4 changes: 2 additions & 2 deletions log/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions log/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions node/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
7 changes: 6 additions & 1 deletion node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
zlacfzy marked this conversation as resolved.
Show resolved Hide resolved
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 {
Expand Down
Loading