package runtimelogs import ( "context" "fmt" "log" "strings" "sync" "time" "evobgp/internal/store" "github.com/robfig/cron/v3" ) // SchedulerDeps configures the runtime log auto-cleanup background scheduler. type SchedulerDeps struct { Service *Service Store store.Backend PolicyTenant string } // ResolvePolicyTenant picks the tenant whose settings drive auto-cleanup. func ResolvePolicyTenant(st store.Backend, explicit string) (string, error) { explicit = strings.TrimSpace(explicit) if explicit != "" { return explicit, nil } if st == nil { return "", fmt.Errorf("runtimelogs: store required") } ids, err := st.ListTenantIDs() if err != nil { return "", err } for _, id := range ids { settings, err := st.ListGlobalSettings(id) if err != nil { continue } if PolicyFromSettings(settings).Enabled { return id, nil } } if len(ids) > 0 { return ids[0], nil } return "", fmt.Errorf("runtimelogs: no tenants configured") } // StartAutoCleanupScheduler runs periodic auto-cleanup on evobgp-all when FS is available. func StartAutoCleanupScheduler(ctx context.Context, deps SchedulerDeps, tick time.Duration) { if deps.Service == nil || !deps.Service.Available() { log.Printf("runtimelogs: auto-cleanup scheduler disabled (FS unavailable)") return } if deps.Store == nil { log.Printf("runtimelogs: auto-cleanup scheduler disabled (no store)") return } if tick <= 0 { tick = 30 * time.Second } go func() { parser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow) var mu sync.Mutex lastFired := map[string]time.Time{} t := time.NewTicker(tick) defer t.Stop() for { select { case <-ctx.Done(): return case <-t.C: tenantID, err := ResolvePolicyTenant(deps.Store, deps.PolicyTenant) if err != nil { continue } settings, err := deps.Store.ListGlobalSettings(tenantID) if err != nil { continue } policy := PolicyFromSettings(settings) if !policy.Enabled { continue } sched, err := parser.Parse(policy.Schedule) if err != nil { log.Printf("runtimelogs: invalid cron %q: %v", policy.Schedule, err) continue } now := time.Now().UTC() mu.Lock() prev := lastFired[policy.Schedule] if prev.IsZero() { prev = now.Add(-time.Minute) } next := sched.Next(prev) if next.After(now) { mu.Unlock() continue } slot := next.Unix() / 60 if lf, ok := lastFired[policy.Schedule]; ok && lf.Unix()/60 == slot { mu.Unlock() continue } lastFired[policy.Schedule] = next mu.Unlock() _, err = RunAutoCleanup(ctx, AutoCleanupDeps{ Service: deps.Service, Store: deps.Store, TenantID: tenantID, }, policy, false, "scheduler") if err != nil { log.Printf("runtimelogs: auto-cleanup run failed: %v", err) } } } }() log.Printf("runtimelogs: auto-cleanup scheduler started (tick=%s)", tick) }