ca2a8c8aa7
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>
67 lines
1.9 KiB
Go
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
|
|
}
|