Files
gosentry/src/runner/cleanup.go
T
mix ca2a8c8aa7 fix: accept zero retention limits, retire Store() for typed accessors
Phase 8 (PROJECT_REVIEW_PLAN.md 8.1): 0 in MaxLogFiles/MaxLogAgeDays now
means "keep everything" end to end. runner.CleanupLogs already treated
<= 0 as disabled; validateConfig, the Settings form, and
loadOrCreateConfig's backfill were the only things making that state
unreachable.

Phase 9 (1.1, rolling up 1.2, 1.3, 7.3): added Service.Config() and
Service.Paths(), copying under mu, and converted every UI site that read
Service state through the raw *storage.Store returned by Store() (now
removed). jobs_view's pause control is now driven by refreshView reading
svc.Config().Paused on every event instead of only mirroring its own tap
handler, which makes it an actual consumer of SchedulerStateChanged.
mainwindow's event listener is a real type switch, and events.go's doc
comment no longer claims a compiler exhaustiveness check Go doesn't have.
Unexported the redundant SetAutostart/AutostartStatus package functions
in platform/autostart now that only the Manager methods are used outside
the package.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 22:15:19 +03:00

67 lines
1.9 KiB
Go

package runner
import (
"errors"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
// CleanupLogs enforces the count and age retention policies on the .log files
// in logsDir. maxFiles <= 0 disables the count policy and maxAgeDays <= 0
// disables the age policy, independently — "keep everything" is a value the
// user can choose in Settings, not just an internal default (STANDARDS
// §Intentional behavior).
func CleanupLogs(logsDir string, maxFiles int, maxAgeDays int) error {
entries, err := os.ReadDir(logsDir)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil
}
return err
}
type logFile struct {
path string
modTime time.Time
}
var logs []logFile
cutoff := time.Now().AddDate(0, 0, -maxAgeDays)
for _, entry := range entries {
// Only GoSentry run logs are managed here. Directories and non-.log files
// are intentionally ignored so the user can keep notes or other artifacts
// in the same folder without the cleanup policy deleting them.
if entry.IsDir() || !strings.HasSuffix(strings.ToLower(entry.Name()), ".log") {
continue
}
path := filepath.Join(logsDir, entry.Name())
info, err := entry.Info()
if err != nil {
continue
}
if maxAgeDays > 0 && info.ModTime().Before(cutoff) {
// Cleanup is best-effort: failing to delete one file should not block
// the scheduler from running future jobs.
_ = os.Remove(path)
continue
}
logs = append(logs, logFile{path: path, modTime: info.ModTime()})
}
if maxFiles <= 0 || len(logs) <= maxFiles {
return nil
}
sort.Slice(logs, func(i int, j int) bool {
// Newest files are kept first, then everything after maxFiles is removed.
// This matches the user's expectation that the most recent failures and
// command output remain available for investigation.
return logs[i].modTime.After(logs[j].modTime)
})
for _, old := range logs[maxFiles:] {
_ = os.Remove(old.path)
}
return nil
}