218 lines
4.3 KiB
Go
218 lines
4.3 KiB
Go
package alerting
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"sync"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// AlertLevel represents alert severity
|
||
|
|
type AlertLevel string
|
||
|
|
|
||
|
|
const (
|
||
|
|
AlertWarning AlertLevel = "warning"
|
||
|
|
AlertError AlertLevel = "error"
|
||
|
|
AlertCritical AlertLevel = "critical"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Alert represents an alert notification
|
||
|
|
type Alert struct {
|
||
|
|
ID string
|
||
|
|
Level AlertLevel
|
||
|
|
Title string
|
||
|
|
Message string
|
||
|
|
Timestamp time.Time
|
||
|
|
Resolved bool
|
||
|
|
Source string
|
||
|
|
}
|
||
|
|
|
||
|
|
// AlertRule represents a rule that triggers alerts
|
||
|
|
type AlertRule struct {
|
||
|
|
ID string
|
||
|
|
Name string
|
||
|
|
Threshold float64
|
||
|
|
Metric string
|
||
|
|
Level AlertLevel
|
||
|
|
}
|
||
|
|
|
||
|
|
// AlertManager manages alert rules and notifications
|
||
|
|
type AlertManager struct {
|
||
|
|
mu sync.RWMutex
|
||
|
|
rules map[string]*AlertRule
|
||
|
|
alerts map[string]*Alert
|
||
|
|
history []*Alert
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewAlertManager creates a new alert manager
|
||
|
|
func NewAlertManager() *AlertManager {
|
||
|
|
return &AlertManager{
|
||
|
|
rules: make(map[string]*AlertRule),
|
||
|
|
alerts: make(map[string]*Alert),
|
||
|
|
history: make([]*Alert, 0),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// AddRule adds an alert rule
|
||
|
|
func (am *AlertManager) AddRule(rule *AlertRule) error {
|
||
|
|
if rule.ID == "" || rule.Name == "" {
|
||
|
|
return fmt.Errorf("rule ID and name required")
|
||
|
|
}
|
||
|
|
|
||
|
|
am.mu.Lock()
|
||
|
|
defer am.mu.Unlock()
|
||
|
|
|
||
|
|
am.rules[rule.ID] = rule
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// RemoveRule removes an alert rule
|
||
|
|
func (am *AlertManager) RemoveRule(ruleID string) error {
|
||
|
|
am.mu.Lock()
|
||
|
|
defer am.mu.Unlock()
|
||
|
|
|
||
|
|
if _, exists := am.rules[ruleID]; !exists {
|
||
|
|
return fmt.Errorf("rule not found: %s", ruleID)
|
||
|
|
}
|
||
|
|
|
||
|
|
delete(am.rules, ruleID)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// TriggerAlert triggers a new alert
|
||
|
|
func (am *AlertManager) TriggerAlert(title, message string, level AlertLevel) (*Alert, error) {
|
||
|
|
if title == "" {
|
||
|
|
return nil, fmt.Errorf("alert title required")
|
||
|
|
}
|
||
|
|
|
||
|
|
am.mu.Lock()
|
||
|
|
defer am.mu.Unlock()
|
||
|
|
|
||
|
|
alert := &Alert{
|
||
|
|
ID: fmt.Sprintf("alert-%d", len(am.alerts)),
|
||
|
|
Level: level,
|
||
|
|
Title: title,
|
||
|
|
Message: message,
|
||
|
|
Timestamp: time.Now(),
|
||
|
|
Resolved: false,
|
||
|
|
}
|
||
|
|
|
||
|
|
am.alerts[alert.ID] = alert
|
||
|
|
am.history = append(am.history, alert)
|
||
|
|
|
||
|
|
return alert, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// ResolveAlert marks an alert as resolved
|
||
|
|
func (am *AlertManager) ResolveAlert(alertID string) error {
|
||
|
|
am.mu.Lock()
|
||
|
|
defer am.mu.Unlock()
|
||
|
|
|
||
|
|
alert, exists := am.alerts[alertID]
|
||
|
|
if !exists {
|
||
|
|
return fmt.Errorf("alert not found: %s", alertID)
|
||
|
|
}
|
||
|
|
|
||
|
|
alert.Resolved = true
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetActiveAlerts returns all unresolved alerts
|
||
|
|
func (am *AlertManager) GetActiveAlerts() []*Alert {
|
||
|
|
am.mu.RLock()
|
||
|
|
defer am.mu.RUnlock()
|
||
|
|
|
||
|
|
result := make([]*Alert, 0)
|
||
|
|
for _, alert := range am.alerts {
|
||
|
|
if !alert.Resolved {
|
||
|
|
result = append(result, alert)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetAlertsByLevel returns alerts by severity level
|
||
|
|
func (am *AlertManager) GetAlertsByLevel(level AlertLevel) []*Alert {
|
||
|
|
am.mu.RLock()
|
||
|
|
defer am.mu.RUnlock()
|
||
|
|
|
||
|
|
result := make([]*Alert, 0)
|
||
|
|
for _, alert := range am.alerts {
|
||
|
|
if alert.Level == level {
|
||
|
|
result = append(result, alert)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetHistory returns alert history
|
||
|
|
func (am *AlertManager) GetHistory() []*Alert {
|
||
|
|
am.mu.RLock()
|
||
|
|
defer am.mu.RUnlock()
|
||
|
|
|
||
|
|
result := make([]*Alert, len(am.history))
|
||
|
|
copy(result, am.history)
|
||
|
|
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetRules returns all alert rules
|
||
|
|
func (am *AlertManager) GetRules() map[string]*AlertRule {
|
||
|
|
am.mu.RLock()
|
||
|
|
defer am.mu.RUnlock()
|
||
|
|
|
||
|
|
result := make(map[string]*AlertRule)
|
||
|
|
for id, rule := range am.rules {
|
||
|
|
result[id] = rule
|
||
|
|
}
|
||
|
|
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetAlertCount returns total active alert count
|
||
|
|
func (am *AlertManager) GetAlertCount() int {
|
||
|
|
am.mu.RLock()
|
||
|
|
defer am.mu.RUnlock()
|
||
|
|
|
||
|
|
return len(am.alerts)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clear clears all alerts
|
||
|
|
func (am *AlertManager) Clear() {
|
||
|
|
am.mu.Lock()
|
||
|
|
defer am.mu.Unlock()
|
||
|
|
|
||
|
|
am.alerts = make(map[string]*Alert)
|
||
|
|
}
|
||
|
|
|
||
|
|
// EvaluateRule checks if a metric triggers an alert rule
|
||
|
|
func (am *AlertManager) EvaluateRule(ruleID string, metricValue float64) (*Alert, error) {
|
||
|
|
am.mu.Lock()
|
||
|
|
defer am.mu.Unlock()
|
||
|
|
|
||
|
|
rule, exists := am.rules[ruleID]
|
||
|
|
if !exists {
|
||
|
|
return nil, fmt.Errorf("rule not found: %s", ruleID)
|
||
|
|
}
|
||
|
|
|
||
|
|
if metricValue >= rule.Threshold {
|
||
|
|
alert := &Alert{
|
||
|
|
ID: fmt.Sprintf("alert-%d", len(am.alerts)),
|
||
|
|
Level: rule.Level,
|
||
|
|
Title: rule.Name,
|
||
|
|
Message: fmt.Sprintf("Threshold %.2f exceeded: %.2f", rule.Threshold, metricValue),
|
||
|
|
Timestamp: time.Now(),
|
||
|
|
Resolved: false,
|
||
|
|
Source: ruleID,
|
||
|
|
}
|
||
|
|
|
||
|
|
am.alerts[alert.ID] = alert
|
||
|
|
am.history = append(am.history, alert)
|
||
|
|
|
||
|
|
return alert, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil, nil
|
||
|
|
}
|