Files
go-otel/internal/alerts/aod.go
mats 5d8bbcd756
Build and Publish / Metadata (push) Successful in 12s
Build and Publish / BuildAndDeployAmd64 (push) Successful in 2m2s
display notifications
2026-09-23 10:58:42 +02:00

94 lines
2.6 KiB
Go

package alerts
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
)
// AlwaysOnDisplay sends each alert to the session API used by the wall
// display. A session is created for the alert and then given a status and log
// entry so the display can show both the current incident and its details.
type AlwaysOnDisplay struct {
URL string
Client *http.Client
}
func (a *AlwaysOnDisplay) Name() string { return "always-on-display" }
type aodSession struct {
ID string `json:"id"`
}
func (a *AlwaysOnDisplay) Send(ctx context.Context, alert Alert) error {
client := a.Client
if client == nil {
client = &http.Client{Timeout: 15 * time.Second}
}
base := strings.TrimRight(a.URL, "/")
var session aodSession
name := fmt.Sprintf("%s alert: %s", strings.ToUpper(string(alert.Priority())), alert.Spike.Service)
if alert.Test {
name = "TEST alert: " + alert.Spike.Service
}
if err := postJSONDecode(ctx, client, base+"/api/sessions", nil, map[string]string{"name": name}, &session); err != nil {
return err
}
if session.ID == "" {
return fmt.Errorf("always-on-display: session response did not include an id")
}
status := firstLine(alert.Text(""))
if err := postJSON(ctx, client, base+"/api/sessions/"+session.ID+"/status", nil, map[string]string{"status": status}); err != nil {
return err
}
level := "info"
if alert.Priority() == PriorityHigh {
level = "error"
} else if alert.Priority() == PriorityMedium {
level = "warning"
}
return postJSON(ctx, client, base+"/api/sessions/"+session.ID+"/log", nil, map[string]string{
"level": level,
"message": alert.Text(""),
})
}
func firstLine(s string) string {
if i := strings.IndexByte(s, '\n'); i >= 0 {
return s[:i]
}
return s
}
func postJSONDecode(ctx context.Context, client *http.Client, target string, headers map[string]string, body any, out any) error {
raw, err := json.Marshal(body)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, target, strings.NewReader(string(raw)))
if err != nil {
return fmt.Errorf("always-on-display: invalid request")
}
req.Header.Set("Content-Type", "application/json")
for k, v := range headers {
req.Header.Set(k, v)
}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("always-on-display: request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode/100 != 2 {
return fmt.Errorf("always-on-display: HTTP %d", resp.StatusCode)
}
if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
return fmt.Errorf("always-on-display: invalid session response: %w", err)
}
return nil
}