@@ 10,6 10,7 @@ import (
"encoding/json"
"flag"
"fmt"
+ "log/slog"
"net/http"
"os"
"os/signal"
@@ 18,7 19,7 @@ import (
"syscall"
"time"
- "git.xenrox.net/~xenrox/go-log"
+ "git.xenrox.net/~xenrox/go-utils/logging"
"git.xenrox.net/~xenrox/ntfy-alertmanager/cache"
"git.xenrox.net/~xenrox/ntfy-alertmanager/config"
"golang.org/x/text/cases"
@@ 29,7 30,7 @@ var version = "dev"
type bridge struct {
cfg *config.Config
- logger *log.Logger
+ logger *slog.Logger
cache cache.Cache
client *httpClient
}
@@ 72,10 73,13 @@ func (br *bridge) singleAlertNotifications(p *payload) []*notification {
for _, alert := range p.Alerts {
contains, err := br.cache.Contains(alert.Fingerprint, alert.Status)
if err != nil {
- br.logger.Errorf("Failed to lookup alert %q in cache: %v", alert.Fingerprint, err)
+ br.logger.Error("Failed to lookup alert in cache",
+ slog.String("fingerprint", alert.Fingerprint),
+ slog.String("error", err.Error()))
}
if contains {
- br.logger.Debugf("Alert %q skipped: Still in cache", alert.Fingerprint)
+ br.logger.Debug("Alert skipped: Still in cache",
+ slog.String("fingerprint", alert.Fingerprint))
continue
}
@@ 161,7 165,8 @@ func (br *bridge) singleAlertNotifications(p *payload) []*notification {
s := &silenceBody{AlertManagerURL: p.ExternalURL, Labels: alert.Labels}
b, err := json.Marshal(s)
if err != nil {
- br.logger.Errorf("Failed to create silence action: %v", err)
+ br.logger.Error("Failed to create silence action",
+ slog.String("error", err.Error()))
}
n.silenceBody = base64.StdEncoding.EncodeToString(b)
@@ 266,7 271,8 @@ func (br *bridge) multiAlertNotification(p *payload) *notification {
s := &silenceBody{AlertManagerURL: p.ExternalURL, Labels: p.CommonLabels}
b, err := json.Marshal(s)
if err != nil {
- br.logger.Errorf("Failed to create silence action: %v", err)
+ br.logger.Error("Failed to create silence action",
+ slog.String("error", err.Error()))
}
n.silenceBody = base64.StdEncoding.EncodeToString(b)
@@ 332,7 338,8 @@ func (br *bridge) publish(n *notification) error {
if resp.StatusCode != http.StatusOK {
var ntfyError ntfyError
if err := json.NewDecoder(resp.Body).Decode(&ntfyError); err != nil {
- br.logger.Debugf("Publish: failed to decode error: %v", err)
+ br.logger.Debug("Publish: Failed to decode error",
+ slog.String("error", err.Error()))
return fmt.Errorf("ntfy: received status code %d", resp.StatusCode)
}
@@ 347,36 354,39 @@ func (br *bridge) handleWebhooks(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
- br.logger.Debugf("illegal HTTP method: expected %q, got %q", "POST", r.Method)
+ br.logger.Debug(fmt.Sprintf("Illegal HTTP method: expected %q, got %q", "POST", r.Method))
return
}
contentType := r.Header.Get("Content-Type")
if contentType != "application/json" {
http.Error(w, "Only application/json allowed", http.StatusUnsupportedMediaType)
- br.logger.Debugf("illegal content type: %s", contentType)
+ br.logger.Debug(fmt.Sprintf("Illegal content type: %s", contentType))
return
}
var event payload
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
- br.logger.Debug(err)
+ br.logger.Debug("Failed to decode payload",
+ slog.String("error", err.Error()))
return
}
- if br.logger.Level() == log.Debug {
- br.logger.Debugf("Received alert %+v", event)
- }
+ br.logger.Debug("Received alert",
+ slog.Any("payload", event))
if br.cfg.AlertMode == config.Single {
notifications := br.singleAlertNotifications(&event)
for _, n := range notifications {
err := br.publish(n)
if err != nil {
- br.logger.Errorf("Failed to publish notification: %v", err)
+ br.logger.Error("Failed to publish notification",
+ slog.String("error", err.Error()))
} else {
if err := br.cache.Set(n.fingerprint, n.status); err != nil {
- br.logger.Errorf("Failed to set alert %q in cache: %v", n.fingerprint, err)
+ br.logger.Error("Failed to cache alert",
+ slog.String("fingerprint", n.fingerprint),
+ slog.String("error", err.Error()))
}
}
}
@@ 384,7 394,8 @@ func (br *bridge) handleWebhooks(w http.ResponseWriter, r *http.Request) {
notification := br.multiAlertNotification(&event)
err := br.publish(notification)
if err != nil {
- br.logger.Errorf("Failed to publish notification: %v", err)
+ br.logger.Error("Failed to publish notification",
+ slog.String("error", err.Error()))
}
}
}
@@ 439,16 450,24 @@ func main() {
os.Exit(0)
}
- logger := log.NewDefaultLogger()
+ logLevel := new(slog.LevelVar)
+ logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
+ Level: logLevel,
+ }))
cfg, err := config.ReadConfig(configPath)
if err != nil {
- logger.Fatalf("Failed to read config: %v", err)
+ logger.Error("Failed to read config",
+ slog.String("error", err.Error()))
+ os.Exit(1)
}
- if err := logger.SetLevelFromString(cfg.LogLevel); err != nil {
- logger.Errorf("Failed to parse logging level: %v", err)
+ level, err := logging.ParseLevelFromString(cfg.LogLevel)
+ if err != nil {
+ logger.Error("Failed to parse logging level",
+ slog.String("error", err.Error()))
}
+ logLevel.Set(level)
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
@@ 457,11 476,13 @@ func main() {
c, err := cache.NewCache(cfg.Cache)
if err != nil {
- logger.Fatalf("Failed to create cache: %v", err)
+ logger.Error("Failed to create cache",
+ slog.String("error", err.Error()))
+ os.Exit(1)
}
bridge := &bridge{cfg: cfg, logger: logger, cache: c, client: client}
- logger.Infof("Listening on %s, ntfy-alertmanager %s", cfg.HTTPAddress, version)
+ logger.Info(fmt.Sprintf("Listening on %s, ntfy-alertmanager %s", cfg.HTTPAddress, version))
mux := http.NewServeMux()
mux.HandleFunc("/", bridge.handleWebhooks)
@@ 483,7 504,9 @@ func main() {
go func() {
err = httpServer.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
- logger.Fatalf("Failed to start HTTP server: %v", err)
+ logger.Error("Failed to start HTTP server",
+ slog.String("error", err.Error()))
+ os.Exit(1)
}
}()
@@ 495,6 518,7 @@ func main() {
err = httpServer.Shutdown(httpShutdownContext)
if err != nil {
- logger.Errorf("Failed to shutdown HTTP server: %v", err)
+ logger.Error("Failed to shutdown HTTP server",
+ slog.String("error", err.Error()))
}
}
@@ 4,7 4,9 @@ import (
"bytes"
"encoding/base64"
"encoding/json"
+ "fmt"
"io"
+ "log/slog"
"net/http"
"time"
)
@@ 40,26 42,29 @@ func (br *bridge) handleSilences(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
- br.logger.Debugf("silences: illegal HTTP method: expected %q, got %q", "POST", r.Method)
+ br.logger.Debug(fmt.Sprintf("Silences: Illegal HTTP method: expected %q, got %q", "POST", r.Method))
return
}
b, err := io.ReadAll(r.Body)
if err != nil {
- br.logger.Debugf("silences: %v", err)
+ br.logger.Error("Silences: Failed to read body",
+ slog.String("error", err.Error()))
return
}
b, err = base64.StdEncoding.DecodeString(string(b))
if err != nil {
- br.logger.Debugf("silences: %v", err)
+ br.logger.Error("Silences: Failed to decode",
+ slog.String("error", err.Error()))
return
}
var sb silenceBody
err = json.Unmarshal(b, &sb)
if err != nil {
- br.logger.Debugf("silences: %v", err)
+ br.logger.Error("Silences: Failed to unmarshal",
+ slog.String("error", err.Error()))
return
}
@@ 85,7 90,8 @@ func (br *bridge) handleSilences(w http.ResponseWriter, r *http.Request) {
b, err = json.Marshal(silence)
if err != nil {
- br.logger.Debugf("silences: %v", err)
+ br.logger.Error("Silences: Failed to marshal",
+ slog.String("error", err.Error()))
return
}
@@ 97,7 103,8 @@ func (br *bridge) handleSilences(w http.ResponseWriter, r *http.Request) {
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(b))
if err != nil {
- br.logger.Debugf("silences: %v", err)
+ br.logger.Error("Silences: Failed to create request",
+ slog.String("error", err.Error()))
return
}
@@ 109,27 116,32 @@ func (br *bridge) handleSilences(w http.ResponseWriter, r *http.Request) {
req.Header.Add("Content-Type", "application/json")
resp, err := br.client.Do(req)
if err != nil {
- br.logger.Debugf("silences: %v", err)
+ br.logger.Error("Silences: Failed to POST request",
+ slog.String("error", err.Error()))
return
}
defer resp.Body.Close()
b, err = io.ReadAll(resp.Body)
if err != nil {
- br.logger.Debugf("silences: %v", err)
+ br.logger.Error("Silences: Failed to read response body",
+ slog.String("error", err.Error()))
return
}
if resp.StatusCode != http.StatusOK {
- br.logger.Debugf("silences: received status code %d", resp.StatusCode)
+ br.logger.Error("Silences: Received non-200 status code",
+ slog.Int("status", resp.StatusCode))
return
}
var id silenceResponse
if err := json.Unmarshal(b, &id); err != nil {
- br.logger.Debugf("silences: %v", err)
+ br.logger.Error("Silences: Failed to unmarshal response",
+ slog.String("error", err.Error()))
return
}
- br.logger.Infof("Created new silence %s", id.ID)
+ br.logger.Info("Silences: Created new silence",
+ slog.String("ID", id.ID))
}