| 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") |
| } |
|
|