Pulse/internal/alerts/callbacks.go
rcourtman faefe6edc8 Remove 198 unreachable Go functions
Dead-code sweep. Functions flagged unreachable by golang.org/x/tools/cmd/deadcode
and confirmed unused across pulse, pulse-enterprise, pulse-pro and pulse-mobile by
adversarial cross-repo verification. Cross-module reachability was checked
explicitly (only pkg/ exported symbols are importable by other modules; internal/
packages and _test.go files are not). go build, go vet and test-compile all pass.
2026-06-03 12:29:37 +01:00

438 lines
11 KiB
Go

package alerts
import (
"sync"
"github.com/rs/zerolog/log"
)
type callbackBus struct {
mu sync.RWMutex
onAlert func(alert *Alert)
alertSubs map[int]func(alert *Alert)
onAlertForAI func(alert *Alert)
alertForAISubs map[int]func(alert *Alert)
onResolved func(alertID string)
resolvedSubs map[int]func(alertID string)
onAcknowledged func(alert *Alert, user string)
onUnacknowledged func(alert *Alert, user string)
onEscalate func(alert *Alert, level int)
onFlappingDetected func(alert *Alert, trackingKey string)
nextCallbackID int
}
func newCallbackBus() callbackBus {
return callbackBus{
alertSubs: make(map[int]func(*Alert)),
alertForAISubs: make(map[int]func(*Alert)),
resolvedSubs: make(map[int]func(string)),
}
}
func (b *callbackBus) nextIDLocked() int {
b.nextCallbackID++
return b.nextCallbackID
}
func (b *callbackBus) ensureAlertSubsLocked() {
if b.alertSubs == nil {
b.alertSubs = make(map[int]func(*Alert))
}
}
func (b *callbackBus) ensureAlertForAISubsLocked() {
if b.alertForAISubs == nil {
b.alertForAISubs = make(map[int]func(*Alert))
}
}
func (b *callbackBus) ensureResolvedSubsLocked() {
if b.resolvedSubs == nil {
b.resolvedSubs = make(map[int]func(string))
}
}
func (b *callbackBus) setAlertCallback(cb func(alert *Alert)) {
b.mu.Lock()
defer b.mu.Unlock()
b.onAlert = cb
}
func (b *callbackBus) subscribeAlertCallback(cb func(alert *Alert)) func() {
if cb == nil {
return func() {}
}
b.mu.Lock()
b.ensureAlertSubsLocked()
id := b.nextIDLocked()
b.alertSubs[id] = cb
b.mu.Unlock()
return func() {
b.mu.Lock()
delete(b.alertSubs, id)
b.mu.Unlock()
}
}
func (b *callbackBus) setAlertForAICallback(cb func(alert *Alert)) {
b.mu.Lock()
defer b.mu.Unlock()
b.onAlertForAI = cb
}
func (b *callbackBus) subscribeAlertForAICallback(cb func(alert *Alert)) func() {
if cb == nil {
return func() {}
}
b.mu.Lock()
b.ensureAlertForAISubsLocked()
id := b.nextIDLocked()
b.alertForAISubs[id] = cb
b.mu.Unlock()
return func() {
b.mu.Lock()
delete(b.alertForAISubs, id)
b.mu.Unlock()
}
}
func (b *callbackBus) setResolvedCallback(cb func(alertID string)) {
b.mu.Lock()
defer b.mu.Unlock()
b.onResolved = cb
}
func (b *callbackBus) subscribeResolvedCallback(cb func(alertID string)) func() {
if cb == nil {
return func() {}
}
b.mu.Lock()
b.ensureResolvedSubsLocked()
id := b.nextIDLocked()
b.resolvedSubs[id] = cb
b.mu.Unlock()
return func() {
b.mu.Lock()
delete(b.resolvedSubs, id)
b.mu.Unlock()
}
}
func (b *callbackBus) setAcknowledgedCallback(cb func(alert *Alert, user string)) {
b.mu.Lock()
defer b.mu.Unlock()
b.onAcknowledged = cb
}
func (b *callbackBus) setUnacknowledgedCallback(cb func(alert *Alert, user string)) {
b.mu.Lock()
defer b.mu.Unlock()
b.onUnacknowledged = cb
}
func (b *callbackBus) setEscalateCallback(cb func(alert *Alert, level int)) {
b.mu.Lock()
defer b.mu.Unlock()
b.onEscalate = cb
}
func (b *callbackBus) setFlappingDetectedCallback(cb func(alert *Alert, trackingKey string)) {
b.mu.Lock()
defer b.mu.Unlock()
b.onFlappingDetected = cb
}
func (b *callbackBus) alertCallback() func(alert *Alert) {
b.mu.RLock()
cb := b.onAlert
b.mu.RUnlock()
return cb
}
func (b *callbackBus) alertCallbacks() []func(alert *Alert) {
b.mu.RLock()
defer b.mu.RUnlock()
callbacks := make([]func(alert *Alert), 0, len(b.alertSubs)+1)
if b.onAlert != nil {
callbacks = append(callbacks, b.onAlert)
}
for _, cb := range b.alertSubs {
if cb != nil {
callbacks = append(callbacks, cb)
}
}
return callbacks
}
func (b *callbackBus) alertForAICallback() func(alert *Alert) {
b.mu.RLock()
cb := b.onAlertForAI
b.mu.RUnlock()
return cb
}
func (b *callbackBus) alertForAICallbacks() []func(alert *Alert) {
b.mu.RLock()
defer b.mu.RUnlock()
callbacks := make([]func(alert *Alert), 0, len(b.alertForAISubs)+1)
if b.onAlertForAI != nil {
callbacks = append(callbacks, b.onAlertForAI)
}
for _, cb := range b.alertForAISubs {
if cb != nil {
callbacks = append(callbacks, cb)
}
}
return callbacks
}
func (b *callbackBus) resolvedCallbacks() []func(alertID string) {
b.mu.RLock()
defer b.mu.RUnlock()
callbacks := make([]func(alertID string), 0, len(b.resolvedSubs)+1)
if b.onResolved != nil {
callbacks = append(callbacks, b.onResolved)
}
for _, cb := range b.resolvedSubs {
if cb != nil {
callbacks = append(callbacks, cb)
}
}
return callbacks
}
func (b *callbackBus) acknowledgedCallback() func(alert *Alert, user string) {
b.mu.RLock()
cb := b.onAcknowledged
b.mu.RUnlock()
return cb
}
func (b *callbackBus) unacknowledgedCallback() func(alert *Alert, user string) {
b.mu.RLock()
cb := b.onUnacknowledged
b.mu.RUnlock()
return cb
}
func (b *callbackBus) escalateCallback() func(alert *Alert, level int) {
b.mu.RLock()
cb := b.onEscalate
b.mu.RUnlock()
return cb
}
func (b *callbackBus) flappingDetectedCallback() func(alert *Alert, trackingKey string) {
b.mu.RLock()
cb := b.onFlappingDetected
b.mu.RUnlock()
return cb
}
// SetAlertCallback sets the callback for new alerts.
func (m *Manager) SetAlertCallback(cb func(alert *Alert)) {
m.callbacks.setAlertCallback(cb)
}
// SubscribeAlertCallback registers an additional alert callback without
// replacing the legacy single callback slot. The returned function removes the
// subscription when called.
func (m *Manager) SubscribeAlertCallback(cb func(alert *Alert)) func() {
return m.callbacks.subscribeAlertCallback(cb)
}
// SetAlertForAICallback sets a callback for AI analysis when alerts are created.
// Unlike SetAlertCallback, this callback is invoked unconditionally - it bypasses
// activation state, quiet hours, and other notification suppression checks.
// This allows AI to analyze alerts even when the user hasn't finished setup.
func (m *Manager) SetAlertForAICallback(cb func(alert *Alert)) {
m.callbacks.setAlertForAICallback(cb)
log.Info().Msg("alert-for-AI callback registered (bypasses notification suppression)")
}
// SubscribeAlertForAICallback registers an additional AI alert callback without
// replacing the legacy single callback slot. The returned function removes the
// subscription when called.
func (m *Manager) SubscribeAlertForAICallback(cb func(alert *Alert)) func() {
return m.callbacks.subscribeAlertForAICallback(cb)
}
// SetResolvedCallback sets the callback for resolved alerts.
func (m *Manager) SetResolvedCallback(cb func(alertID string)) {
m.callbacks.setResolvedCallback(cb)
}
// SubscribeResolvedCallback registers an additional resolved-alert callback
// without replacing the legacy single callback slot. The returned function
// removes the subscription when called.
func (m *Manager) SubscribeResolvedCallback(cb func(alertID string)) func() {
return m.callbacks.subscribeResolvedCallback(cb)
}
// SetAcknowledgedCallback sets the callback for acknowledged alerts.
func (m *Manager) SetAcknowledgedCallback(cb func(alert *Alert, user string)) {
m.callbacks.setAcknowledgedCallback(cb)
}
// SetUnacknowledgedCallback sets the callback for unacknowledged alerts.
func (m *Manager) SetUnacknowledgedCallback(cb func(alert *Alert, user string)) {
m.callbacks.setUnacknowledgedCallback(cb)
}
// SetEscalateCallback sets the callback for escalated alerts.
func (m *Manager) SetEscalateCallback(cb func(alert *Alert, level int)) {
m.callbacks.setEscalateCallback(cb)
}
// SetFlappingDetectedCallback registers a callback fired exactly once on the
// transition into flapping suppression for a given trackingKey. The callback
// is invoked from a goroutine -- the alerts manager lock is NOT held when it
// runs -- so the callback is free to take its own locks or schedule a patrol.
// It will NOT fire again for the same trackingKey while the flapping cooldown
// window is active; subsequent suppressed dispatches are silent.
func (m *Manager) SetFlappingDetectedCallback(cb func(alert *Alert, trackingKey string)) {
m.callbacks.setFlappingDetectedCallback(cb)
}
func (m *Manager) getAlertCallback() func(alert *Alert) {
return m.callbacks.alertCallback()
}
func (m *Manager) getAlertCallbacks() []func(alert *Alert) {
return m.callbacks.alertCallbacks()
}
func (m *Manager) getAlertForAICallback() func(alert *Alert) {
return m.callbacks.alertForAICallback()
}
func (m *Manager) getAlertForAICallbacks() []func(alert *Alert) {
return m.callbacks.alertForAICallbacks()
}
func (m *Manager) getResolvedCallbacks() []func(alertID string) {
return m.callbacks.resolvedCallbacks()
}
func (m *Manager) getAcknowledgedCallback() func(alert *Alert, user string) {
return m.callbacks.acknowledgedCallback()
}
func (m *Manager) getUnacknowledgedCallback() func(alert *Alert, user string) {
return m.callbacks.unacknowledgedCallback()
}
func (m *Manager) getEscalateCallback() func(alert *Alert, level int) {
return m.callbacks.escalateCallback()
}
// safeCallResolvedAlertCallback invokes onResolved with panic recovery while
// preserving canonical state as the internal identity and emitting the public
// alert ID to external callbacks for compatibility.
func (m *Manager) safeCallResolvedAlertCallback(alert *Alert, fallbackID string, async bool) {
callbacks := m.getResolvedCallbacks()
if len(callbacks) == 0 {
return
}
publicID := exportedAlertID(alert, fallbackID)
trackingKey := canonicalTrackingKeyForAlert(alert)
callbackFunc := func() {
defer func() {
if r := recover(); r != nil {
log.Error().
Interface("panic", r).
Str("alertID", publicID).
Str("trackingKey", trackingKey).
Msg("Panic in onResolved callback")
}
}()
for _, callback := range callbacks {
callback(publicID)
}
}
if async {
go callbackFunc()
} else {
callbackFunc()
}
}
// safeCallAcknowledgedCallback invokes onAcknowledged with panic recovery and alert cloning.
func (m *Manager) safeCallAcknowledgedCallback(alert *Alert, user string) {
callback := m.getAcknowledgedCallback()
if callback == nil || alert == nil {
return
}
alertCopy := cloneAlertForOutput(alert)
go func(a *Alert, u string) {
defer func() {
if r := recover(); r != nil {
log.Error().
Interface("panic", r).
Str("alertID", a.ID).
Msg("Panic in onAcknowledged callback")
}
}()
callback(a, u)
}(alertCopy, user)
}
// safeCallUnacknowledgedCallback invokes onUnacknowledged with panic recovery and alert cloning.
func (m *Manager) safeCallUnacknowledgedCallback(alert *Alert, user string) {
callback := m.getUnacknowledgedCallback()
if callback == nil || alert == nil {
return
}
alertCopy := cloneAlertForOutput(alert)
go func(a *Alert, u string) {
defer func() {
if r := recover(); r != nil {
log.Error().
Interface("panic", r).
Str("alertID", a.ID).
Msg("Panic in onUnacknowledged callback")
}
}()
callback(a, u)
}(alertCopy, user)
}
// safeCallEscalateCallback invokes onEscalate with panic recovery and alert cloning.
func (m *Manager) safeCallEscalateCallback(alert *Alert, level int) {
callback := m.getEscalateCallback()
if callback == nil {
return
}
alertCopy := cloneAlertForOutput(alert)
go func(a *Alert, lvl int) {
defer func() {
if r := recover(); r != nil {
log.Error().
Interface("panic", r).
Str("alertID", a.ID).
Int("level", lvl).
Msg("Panic in onEscalate callback")
}
}()
callback(a, lvl)
}(alertCopy, level)
}