feat(config): add log file size limit configuration #535

This commit introduces a new configuration option `logs-max-total-size-mb` that allows users to set a maximum total size (in MB) for log files in the logs directory. When this limit is exceeded, the oldest log files will be automatically deleted to stay within the specified size. Setting this value to 0 (the default) disables this feature. This change enhances log management by preventing excessive disk space usage.
This commit is contained in:
Luis Pater
2025-12-20 15:52:59 +08:00
parent 8a5db02165
commit 3e4858a624
7 changed files with 282 additions and 16 deletions

View File

@@ -0,0 +1,166 @@
package logging
import (
"context"
"os"
"path/filepath"
"sort"
"strings"
"time"
log "github.com/sirupsen/logrus"
)
const logDirCleanerInterval = time.Minute
var logDirCleanerCancel context.CancelFunc
func configureLogDirCleanerLocked(logDir string, maxTotalSizeMB int, protectedPath string) {
stopLogDirCleanerLocked()
if maxTotalSizeMB <= 0 {
return
}
maxBytes := int64(maxTotalSizeMB) * 1024 * 1024
if maxBytes <= 0 {
return
}
dir := strings.TrimSpace(logDir)
if dir == "" {
return
}
ctx, cancel := context.WithCancel(context.Background())
logDirCleanerCancel = cancel
go runLogDirCleaner(ctx, filepath.Clean(dir), maxBytes, strings.TrimSpace(protectedPath))
}
func stopLogDirCleanerLocked() {
if logDirCleanerCancel == nil {
return
}
logDirCleanerCancel()
logDirCleanerCancel = nil
}
func runLogDirCleaner(ctx context.Context, logDir string, maxBytes int64, protectedPath string) {
ticker := time.NewTicker(logDirCleanerInterval)
defer ticker.Stop()
cleanOnce := func() {
deleted, errClean := enforceLogDirSizeLimit(logDir, maxBytes, protectedPath)
if errClean != nil {
log.WithError(errClean).Warn("logging: failed to enforce log directory size limit")
return
}
if deleted > 0 {
log.Debugf("logging: removed %d old log file(s) to enforce log directory size limit", deleted)
}
}
cleanOnce()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
cleanOnce()
}
}
}
func enforceLogDirSizeLimit(logDir string, maxBytes int64, protectedPath string) (int, error) {
if maxBytes <= 0 {
return 0, nil
}
dir := strings.TrimSpace(logDir)
if dir == "" {
return 0, nil
}
dir = filepath.Clean(dir)
entries, errRead := os.ReadDir(dir)
if errRead != nil {
if os.IsNotExist(errRead) {
return 0, nil
}
return 0, errRead
}
protected := strings.TrimSpace(protectedPath)
if protected != "" {
protected = filepath.Clean(protected)
}
type logFile struct {
path string
size int64
modTime time.Time
}
var (
files []logFile
total int64
)
for _, entry := range entries {
if entry.IsDir() {
continue
}
name := entry.Name()
if !isLogFileName(name) {
continue
}
info, errInfo := entry.Info()
if errInfo != nil {
continue
}
if !info.Mode().IsRegular() {
continue
}
path := filepath.Join(dir, name)
files = append(files, logFile{
path: path,
size: info.Size(),
modTime: info.ModTime(),
})
total += info.Size()
}
if total <= maxBytes {
return 0, nil
}
sort.Slice(files, func(i, j int) bool {
return files[i].modTime.Before(files[j].modTime)
})
deleted := 0
for _, file := range files {
if total <= maxBytes {
break
}
if protected != "" && filepath.Clean(file.path) == protected {
continue
}
if errRemove := os.Remove(file.path); errRemove != nil {
log.WithError(errRemove).Warnf("logging: failed to remove old log file: %s", filepath.Base(file.path))
continue
}
total -= file.size
deleted++
}
return deleted, nil
}
func isLogFileName(name string) bool {
trimmed := strings.TrimSpace(name)
if trimmed == "" {
return false
}
lower := strings.ToLower(trimmed)
return strings.HasSuffix(lower, ".log") || strings.HasSuffix(lower, ".log.gz")
}