package queue import ( "sync" "time" ) type HealthReporter struct { mu sync.RWMutex startedAt time.Time lastPollAt time.Time lastSuccessAt time.Time lastFailureAt time.Time lastError string consecutivePollFailures int errorThreshold int draining bool } type HealthSnapshot struct { Status string `json:"status"` StartedAt time.Time `json:"started_at"` LastPollAt time.Time `json:"last_poll_at,omitempty"` LastSuccessAt time.Time `json:"last_success_at,omitempty"` LastFailureAt time.Time `json:"last_failure_at,omitempty"` LastError string `json:"last_error,omitempty"` ConsecutivePollFailure int `json:"consecutive_poll_failures"` Draining bool `json:"draining"` } func NewHealthReporter(errorThreshold int) *HealthReporter { if errorThreshold <= 0 { errorThreshold = 3 } now := time.Now().UTC() return &HealthReporter{ startedAt: now, lastPollAt: now, errorThreshold: errorThreshold, } } func (r *HealthReporter) MarkPollSuccess() { r.mu.Lock() defer r.mu.Unlock() now := time.Now().UTC() r.lastPollAt = now r.lastSuccessAt = now r.consecutivePollFailures = 0 r.lastError = "" } func (r *HealthReporter) MarkPollFailure(err error) { r.mu.Lock() defer r.mu.Unlock() now := time.Now().UTC() r.lastPollAt = now r.lastFailureAt = now r.consecutivePollFailures++ if err != nil { r.lastError = err.Error() } } func (r *HealthReporter) MarkDraining() { r.mu.Lock() defer r.mu.Unlock() r.draining = true } func (r *HealthReporter) Snapshot() HealthSnapshot { r.mu.RLock() defer r.mu.RUnlock() status := "ok" if r.draining { status = "draining" } else if r.consecutivePollFailures >= r.errorThreshold { status = "degraded" } return HealthSnapshot{ Status: status, StartedAt: r.startedAt, LastPollAt: r.lastPollAt, LastSuccessAt: r.lastSuccessAt, LastFailureAt: r.lastFailureAt, LastError: r.lastError, ConsecutivePollFailure: r.consecutivePollFailures, Draining: r.draining, } }