package logfile import ( "os" "path/filepath" "slices" "strings" "time" ) // DefaultKeepLogs is the number of newest prior logs kept regardless of age. // Each process start writes a new log, so frequent restarts would otherwise // accumulate logs for the whole retention window. const DefaultKeepLogs = 8 // PruneOldLogs deletes regular .log files under dir whose mtime is older // than maxAge relative to now, and all but the keep newest of the rest. // A non-positive keep disables the count limit. Logs held open by a running // writer (see AttachLogFiles) are never removed. Returns the number of files // removed and the first error encountered (other failures are still // attempted). // // Non-existent dir is treated as success with zero removed files. // Subdirectories and non-.log entries are skipped. Symlinks are not // followed; their own metadata is consulted via os.Lstat semantics from // os.ReadDir's DirEntry. func PruneOldLogs(dir string, maxAge time.Duration, keep int, now time.Time) (int, error) { // Read the log directory, treating a missing directory as empty. entries, err := os.ReadDir(dir) if err != nil { if os.IsNotExist(err) { return 0, nil } return 0, err } // Collect regular log files; a failed stat is reported but skipped. var logs []os.FileInfo var firstErr error for _, entry := range entries { if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".log") { continue } info, err := entry.Info() if err != nil { if firstErr == nil { firstErr = err } continue } if info.Mode().IsRegular() { logs = append(logs, info) } } // Newest first: keep the first keep logs younger than the cutoff. slices.SortFunc(logs, func(a, b os.FileInfo) int { return b.ModTime().Compare(a.ModTime()) }) cutoff := now.Add(-maxAge) var removed int for i, info := range logs { if !info.ModTime().Before(cutoff) && (keep <= 0 || i < keep) { continue } // Skip logs still held open by a running writer (e.g. a daemon). path := filepath.Join(dir, info.Name()) if logInUse(path) { continue } if err := os.Remove(path); err != nil { if firstErr == nil { firstErr = err } continue } removed++ } return removed, firstErr }