Files
rcourtman 9572a113ae feat(alerts): append-only alert event log with suppression reasons
Adds internal/alerts/eventlog: a SQLite-backed, additive event log that
records lifecycle transitions (resolved, acknowledged, unacknowledged,
escalated, flapping detected) and notification decisions (dispatched,
deferred by quiet hours, suppressed — with the mechanism that held
them). Appends never block alert evaluation: a full buffer drops the
event and counts the drop; a store that fails to open degrades to
recording nothing. 90-day retention, hourly prune.

The manager emits at the existing funnels only — dispatchAlert and the
safe-call resolve/ack/escalate seams — so no lifecycle behavior
changes. Lifecycle "fired" is deliberately not recorded yet: the
active-alert store funnel also runs on persisted restore, so firing
waits for the explicit activation seam in a later phase. The monitoring
bootstrap enables the log per manager; ephemeral managers and tests
record nothing unless they opt in.

GET /api/alerts/events (monitoring:read) reads the log with
alertIdentifier/type/since/limit filters, newest first.

Phase 0 of docs/ALERT_ENGINE_EVOLUTION.md (coverage gap
alert-engine-suppression-observability).
2026-08-26 21:15:37 +01:00

44 lines
851 B
Go

package alerts
import (
"github.com/rs/zerolog/log"
)
// Stop stops the alert manager and saves history
func (m *Manager) Stop() {
m.stopOnce.Do(func() {
m.stopMu.Lock()
m.stopping = true
closeSignalChannel(m.escalationStop)
closeSignalChannel(m.cleanupStop)
m.stopMu.Unlock()
m.workerWG.Wait()
if m.historyManager != nil {
m.historyManager.Stop()
}
// Save active alerts before stopping
if err := m.SaveActiveAlerts(); err != nil {
log.Error().Err(err).Msg("Failed to save active alerts on stop")
}
// Drain and close the event log last so shutdown-time transitions
// are still recorded.
m.SetEventLog(nil)
})
}
func closeSignalChannel(ch chan struct{}) {
if ch == nil {
return
}
defer func() {
if recover() != nil {
// Channel was already closed by another shutdown path.
}
}()
close(ch)
}