feat: logx support logs rotation based on size limitation. (#1652) (#2167)

* feat: logx support logs rotation based on size limitation. (#1652)

implementation of #1652

Totally compatible with the old logx.LogConf. No effect if users do not change their options.

* feat: logx support logs rotation based on size limitation. (#1652)

implementation of #1652

Totally compatible with the old logx.LogConf. No effect if users do not change their options.

* feat: logx support logs rotation based on size limitation. (#1652)

implementation of #1652

Totally compatible with the old logx.LogConf. No effect if users do not change their options.

* feat: logx support logs rotation based on size limitation. (#1652)

implementation of #1652

Totally compatible with the old logx.LogConf. No effect if users do not change their options.
This commit is contained in:
SgtDaJim
2022-07-22 21:13:10 +08:00
committed by GitHub
parent f630bc735b
commit 101304be53
7 changed files with 406 additions and 34 deletions

View File

@@ -9,6 +9,7 @@ import (
"os"
"path"
"path/filepath"
"sort"
"strings"
"sync"
"time"
@@ -18,11 +19,14 @@ import (
)
const (
dateFormat = "2006-01-02"
hoursPerDay = 24
bufferSize = 100
defaultDirMode = 0o755
defaultFileMode = 0o600
rfc3339DateFormat = time.RFC3339
dateFormat = "2006-01-02"
hoursPerDay = 24
bufferSize = 100
defaultDirMode = 0o755
defaultFileMode = 0o600
gzipExt = ".gz"
megabyte = 1024 * 1024
)
// ErrLogFileClosed is an error that indicates the log file is already closed.
@@ -34,7 +38,7 @@ type (
BackupFileName() string
MarkRotated()
OutdatedFiles() []string
ShallRotate() bool
ShallRotate(currentSize, writeLen int) bool
}
// A RotateLogger is a Logger that can rotate log files with given rules.
@@ -49,6 +53,8 @@ type (
// can't use threading.RoutineGroup because of cycle import
waitGroup sync.WaitGroup
closeOnce sync.Once
currentSize int
}
// A DailyRotateRule is a rule to daily rotate the log files.
@@ -59,6 +65,13 @@ type (
days int
gzip bool
}
// SizeLimitRotateRule a rotation rule that make the log file rotated base on size
SizeLimitRotateRule struct {
DailyRotateRule
maxSize int
maxBackups int
}
)
// DefaultRotateRule is a default log rotating rule, currently DailyRotateRule.
@@ -90,7 +103,7 @@ func (r *DailyRotateRule) OutdatedFiles() []string {
var pattern string
if r.gzip {
pattern = fmt.Sprintf("%s%s*.gz", r.filename, r.delimiter)
pattern = fmt.Sprintf("%s%s*%s", r.filename, r.delimiter, gzipExt)
} else {
pattern = fmt.Sprintf("%s%s*", r.filename, r.delimiter)
}
@@ -105,7 +118,7 @@ func (r *DailyRotateRule) OutdatedFiles() []string {
boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay*r.days)).Format(dateFormat)
fmt.Fprintf(&buf, "%s%s%s", r.filename, r.delimiter, boundary)
if r.gzip {
buf.WriteString(".gz")
buf.WriteString(gzipExt)
}
boundaryFile := buf.String()
@@ -120,10 +133,100 @@ func (r *DailyRotateRule) OutdatedFiles() []string {
}
// ShallRotate checks if the file should be rotated.
func (r *DailyRotateRule) ShallRotate() bool {
func (r *DailyRotateRule) ShallRotate(currentSize, writeLen int) bool {
return len(r.rotatedTime) > 0 && getNowDate() != r.rotatedTime
}
// NewSizeLimitRotateRule returns the rotation rule with size limit
func NewSizeLimitRotateRule(filename, delimiter string, days, maxSize, maxBackups int, gzip bool) RotateRule {
return &SizeLimitRotateRule{
DailyRotateRule: DailyRotateRule{
rotatedTime: getNowDateInRFC3339Format(),
filename: filename,
delimiter: delimiter,
days: days,
gzip: gzip,
},
maxSize: maxSize,
maxBackups: maxBackups,
}
}
func (r *SizeLimitRotateRule) ShallRotate(currentSize, writeLen int) bool {
return r.maxSize > 0 && r.maxSize*megabyte < currentSize+writeLen
}
func (r *SizeLimitRotateRule) parseFilename(file string) (dir, logname, ext, prefix string) {
dir = filepath.Dir(r.filename)
logname = filepath.Base(r.filename)
ext = filepath.Ext(r.filename)
prefix = logname[:len(logname)-len(ext)]
return
}
func (r *SizeLimitRotateRule) BackupFileName() string {
dir := filepath.Dir(r.filename)
_, _, ext, prefix := r.parseFilename(r.filename)
timestamp := getNowDateInRFC3339Format()
return filepath.Join(dir, fmt.Sprintf("%s%s%s%s", prefix, r.delimiter, timestamp, ext))
}
func (r *SizeLimitRotateRule) MarkRotated() {
r.rotatedTime = getNowDateInRFC3339Format()
}
func (r *SizeLimitRotateRule) OutdatedFiles() []string {
var pattern string
dir, _, ext, prefix := r.parseFilename(r.filename)
if r.gzip {
pattern = fmt.Sprintf("%s%s%s%s*%s%s", dir, string(filepath.Separator), prefix, r.delimiter, ext, gzipExt)
} else {
pattern = fmt.Sprintf("%s%s%s%s*%s", dir, string(filepath.Separator), prefix, r.delimiter, ext)
}
files, err := filepath.Glob(pattern)
if err != nil {
fmt.Printf("failed to delete outdated log files, error: %s\n", err)
Errorf("failed to delete outdated log files, error: %s", err)
return nil
}
sort.Strings(files)
outdated := make(map[string]lang.PlaceholderType)
// test if too many backups
if r.maxBackups > 0 && len(files) > r.maxBackups {
for _, f := range files[:len(files)-r.maxBackups] {
outdated[f] = lang.Placeholder
}
files = files[len(files)-r.maxBackups:]
}
// test if any too old backups
if r.days > 0 {
boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay*r.days)).Format(rfc3339DateFormat)
bf := filepath.Join(dir, fmt.Sprintf("%s%s%s%s", prefix, r.delimiter, boundary, ext))
if r.gzip {
bf += gzipExt
}
for _, f := range files {
if f < bf {
outdated[f] = lang.Placeholder
} else {
// Becase the filenames are sorted. No need to keep looping after the first ineligible item showing up.
break
}
}
}
var result []string
for k := range outdated {
result = append(result, k)
}
return result
}
// NewLogger returns a RotateLogger with given filename and rule, etc.
func NewLogger(filename string, rule RotateRule, compress bool) (*RotateLogger, error) {
l := &RotateLogger{
@@ -282,15 +385,17 @@ func (l *RotateLogger) startWorker() {
}
func (l *RotateLogger) write(v []byte) {
if l.rule.ShallRotate() {
if l.rule.ShallRotate(l.currentSize, len(v)) {
if err := l.rotate(); err != nil {
log.Println(err)
} else {
l.rule.MarkRotated()
l.currentSize = 0
}
}
if l.fp != nil {
l.fp.Write(v)
l.currentSize += len(v)
}
}
@@ -308,6 +413,10 @@ func getNowDate() string {
return time.Now().Format(dateFormat)
}
func getNowDateInRFC3339Format() string {
return time.Now().Format(rfc3339DateFormat)
}
func gzipFile(file string) error {
in, err := os.Open(file)
if err != nil {
@@ -315,7 +424,7 @@ func gzipFile(file string) error {
}
defer in.Close()
out, err := os.Create(fmt.Sprintf("%s.gz", file))
out, err := os.Create(fmt.Sprintf("%s%s", file, gzipExt))
if err != nil {
return err
}