2025-04-01 10:38:02 +09:00

150 lines
4.0 KiB
Go

package main
import (
"encoding/json"
"io"
"log"
"net/http"
"strings"
"sync"
"time"
)
type Event struct {
Status string `json:"status"`
TimeNow time.Time `json:"timeNow"`
StartsAt time.Time `json:"startsAt"`
Node string `json:"node"`
DeltaLastSeconds float64 `json:"deltaLastSeconds"`
DeltaStartSeconds float64 `json:"deltaStartSeconds"`
}
type Notification struct {
Alerts []Alert `json:"alerts"`
CommonAnnotations map[string]string `json:"commonAnnotations"`
CommonLabels map[string]string `json:"commonLabels"`
ExternalURL string `json:"externalURL"`
GroupKey string `json:"groupKey"`
GroupLabels map[string]string `json:"groupLabels"`
Message string `json:"message"`
OrgID int `json:"orgId"`
Receiver string `json:"receiver"`
State string `json:"state"`
Status string `json:"status"`
Title string `json:"title"`
TruncatedAlerts int `json:"truncatedAlerts"`
Version string `json:"version"`
}
type Alert struct {
Annotations map[string]string `json:"annotations"`
DashboardURL string `json:"dashboardURL"`
StartsAt time.Time `json:"startsAt"`
EndsAt time.Time `json:"endsAt"`
Fingerprint string `json:"fingerprint"`
GeneratorURL string `json:"generatorURL"`
Labels map[string]string `json:"labels"`
PanelURL string `json:"panelURL"`
SilenceURL string `json:"silenceURL"`
Status string `json:"status"`
ValueString string `json:"valueString"`
Values map[string]any `json:"values"`
}
type NotificationHandler struct {
startedAt time.Time
stats map[string]int
hist []Event
m sync.Mutex
}
func NewNotificationHandler() *NotificationHandler {
return &NotificationHandler{
startedAt: time.Now(),
stats: make(map[string]int),
hist: make([]Event, 0),
}
}
func (ah *NotificationHandler) Notify(w http.ResponseWriter, r *http.Request) {
b, err := io.ReadAll(r.Body)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
n := Notification{}
if err := json.Unmarshal(b, &n); err != nil {
log.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
log.Printf("got notification from: %s. a: %v", r.RemoteAddr, n)
ah.m.Lock()
defer ah.m.Unlock()
addr := r.RemoteAddr
if split := strings.Split(r.RemoteAddr, ":"); len(split) > 0 {
addr = split[0]
}
a := n.Alerts[0]
timeNow := time.Now()
ah.stats[n.Status]++
var d time.Duration
if len(ah.hist) > 0 {
last := ah.hist[len(ah.hist)-1]
d = timeNow.Sub(last.TimeNow)
}
ah.hist = append(ah.hist, Event{
Status: n.Status,
StartsAt: a.StartsAt,
TimeNow: timeNow,
Node: addr,
DeltaLastSeconds: d.Seconds(),
DeltaStartSeconds: timeNow.Sub(ah.startedAt).Seconds(),
})
}
func (ah *NotificationHandler) GetNotifications(w http.ResponseWriter, _ *http.Request) {
ah.m.Lock()
defer ah.m.Unlock()
w.Header().Set("Content-Type", "application/json")
res, err := json.MarshalIndent(map[string]any{"stats": ah.stats, "history": ah.hist}, "", "\t")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
//nolint:errcheck
w.Write([]byte(`{"error":"failed to marshal alerts"}`))
log.Printf("failed to marshal alerts: %v\n", err)
return
}
log.Printf("requested current state\n%v\n", string(res))
_, err = w.Write(res)
if err != nil {
log.Printf("failed to write response: %v\n", err)
}
}
func main() {
ah := NewNotificationHandler()
http.HandleFunc("/ready", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})
http.HandleFunc("/notify", ah.Notify)
http.HandleFunc("/notifications", ah.GetNotifications)
log.Println("Listening")
//nolint:errcheck
http.ListenAndServe("0.0.0.0:8080", nil)
}