Use "log/slog" for logging
This commit is contained in:
parent
057b2e15f6
commit
dab77f6393
4 changed files with 75 additions and 39 deletions
4
go.mod
4
go.mod
|
@ -1,10 +1,10 @@
|
||||||
module git.xenrox.net/~xenrox/ntfy-alertmanager
|
module git.xenrox.net/~xenrox/ntfy-alertmanager
|
||||||
|
|
||||||
go 1.21
|
go 1.21.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.sr.ht/~emersion/go-scfg v0.0.0-20230601130942-e042ab15616e
|
git.sr.ht/~emersion/go-scfg v0.0.0-20230601130942-e042ab15616e
|
||||||
git.xenrox.net/~xenrox/go-log v0.0.0-20221012231312-9e7356c29b74
|
git.xenrox.net/~xenrox/go-utils v0.0.0-20230813014007-897165f99b2e
|
||||||
github.com/redis/go-redis/v9 v9.0.5
|
github.com/redis/go-redis/v9 v9.0.5
|
||||||
golang.org/x/text v0.12.0
|
golang.org/x/text v0.12.0
|
||||||
)
|
)
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -1,7 +1,7 @@
|
||||||
git.sr.ht/~emersion/go-scfg v0.0.0-20230601130942-e042ab15616e h1:42zyo0ZFxHGkysM1B9EM7PnQNO0TEzPm+bw/2Zontyg=
|
git.sr.ht/~emersion/go-scfg v0.0.0-20230601130942-e042ab15616e h1:42zyo0ZFxHGkysM1B9EM7PnQNO0TEzPm+bw/2Zontyg=
|
||||||
git.sr.ht/~emersion/go-scfg v0.0.0-20230601130942-e042ab15616e/go.mod h1:ybgvEJTIx5XbaspSviB3KNa6OdPmAZqDoSud7z8fFlw=
|
git.sr.ht/~emersion/go-scfg v0.0.0-20230601130942-e042ab15616e/go.mod h1:ybgvEJTIx5XbaspSviB3KNa6OdPmAZqDoSud7z8fFlw=
|
||||||
git.xenrox.net/~xenrox/go-log v0.0.0-20221012231312-9e7356c29b74 h1:t/52xLRU4IHd2O1nkb9fcUE6K95/KdBtdQYHT31szps=
|
git.xenrox.net/~xenrox/go-utils v0.0.0-20230813014007-897165f99b2e h1:xqkh37YE78gxnJdlLrHc1WCRx3aYE/7XG8uZ6IJWCn0=
|
||||||
git.xenrox.net/~xenrox/go-log v0.0.0-20221012231312-9e7356c29b74/go.mod h1:d98WFDHGpxaEThKue5CfGtr9OrWgbaApprt3GH+OM4s=
|
git.xenrox.net/~xenrox/go-utils v0.0.0-20230813014007-897165f99b2e/go.mod h1:BM4sMPD0fqFB6eG1T/7rGgEUiqZsMpHvq4PGE861Sfk=
|
||||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||||
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
||||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||||
|
|
72
main.go
72
main.go
|
@ -10,6 +10,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
@ -18,7 +19,7 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"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/cache"
|
||||||
"git.xenrox.net/~xenrox/ntfy-alertmanager/config"
|
"git.xenrox.net/~xenrox/ntfy-alertmanager/config"
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
|
@ -29,7 +30,7 @@ var version = "dev"
|
||||||
|
|
||||||
type bridge struct {
|
type bridge struct {
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
logger *log.Logger
|
logger *slog.Logger
|
||||||
cache cache.Cache
|
cache cache.Cache
|
||||||
client *httpClient
|
client *httpClient
|
||||||
}
|
}
|
||||||
|
@ -72,10 +73,13 @@ func (br *bridge) singleAlertNotifications(p *payload) []*notification {
|
||||||
for _, alert := range p.Alerts {
|
for _, alert := range p.Alerts {
|
||||||
contains, err := br.cache.Contains(alert.Fingerprint, alert.Status)
|
contains, err := br.cache.Contains(alert.Fingerprint, alert.Status)
|
||||||
if err != nil {
|
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 {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +165,8 @@ func (br *bridge) singleAlertNotifications(p *payload) []*notification {
|
||||||
s := &silenceBody{AlertManagerURL: p.ExternalURL, Labels: alert.Labels}
|
s := &silenceBody{AlertManagerURL: p.ExternalURL, Labels: alert.Labels}
|
||||||
b, err := json.Marshal(s)
|
b, err := json.Marshal(s)
|
||||||
if err != nil {
|
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)
|
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}
|
s := &silenceBody{AlertManagerURL: p.ExternalURL, Labels: p.CommonLabels}
|
||||||
b, err := json.Marshal(s)
|
b, err := json.Marshal(s)
|
||||||
if err != nil {
|
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)
|
n.silenceBody = base64.StdEncoding.EncodeToString(b)
|
||||||
|
@ -332,7 +338,8 @@ func (br *bridge) publish(n *notification) error {
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
var ntfyError ntfyError
|
var ntfyError ntfyError
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&ntfyError); err != nil {
|
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)
|
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 {
|
if r.Method != http.MethodPost {
|
||||||
http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
contentType := r.Header.Get("Content-Type")
|
contentType := r.Header.Get("Content-Type")
|
||||||
if contentType != "application/json" {
|
if contentType != "application/json" {
|
||||||
http.Error(w, "Only application/json allowed", http.StatusUnsupportedMediaType)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var event payload
|
var event payload
|
||||||
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if br.logger.Level() == log.Debug {
|
br.logger.Debug("Received alert",
|
||||||
br.logger.Debugf("Received alert %+v", event)
|
slog.Any("payload", event))
|
||||||
}
|
|
||||||
|
|
||||||
if br.cfg.AlertMode == config.Single {
|
if br.cfg.AlertMode == config.Single {
|
||||||
notifications := br.singleAlertNotifications(&event)
|
notifications := br.singleAlertNotifications(&event)
|
||||||
for _, n := range notifications {
|
for _, n := range notifications {
|
||||||
err := br.publish(n)
|
err := br.publish(n)
|
||||||
if err != nil {
|
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 {
|
} else {
|
||||||
if err := br.cache.Set(n.fingerprint, n.status); err != nil {
|
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)
|
notification := br.multiAlertNotification(&event)
|
||||||
err := br.publish(notification)
|
err := br.publish(notification)
|
||||||
if err != nil {
|
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)
|
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)
|
cfg, err := config.ReadConfig(configPath)
|
||||||
if err != nil {
|
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 {
|
level, err := logging.ParseLevelFromString(cfg.LogLevel)
|
||||||
logger.Errorf("Failed to parse logging level: %v", err)
|
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)
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer stop()
|
defer stop()
|
||||||
|
@ -457,11 +476,13 @@ func main() {
|
||||||
|
|
||||||
c, err := cache.NewCache(cfg.Cache)
|
c, err := cache.NewCache(cfg.Cache)
|
||||||
if err != nil {
|
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}
|
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 := http.NewServeMux()
|
||||||
mux.HandleFunc("/", bridge.handleWebhooks)
|
mux.HandleFunc("/", bridge.handleWebhooks)
|
||||||
|
@ -483,7 +504,9 @@ func main() {
|
||||||
go func() {
|
go func() {
|
||||||
err = httpServer.ListenAndServe()
|
err = httpServer.ListenAndServe()
|
||||||
if err != nil && err != http.ErrServerClosed {
|
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)
|
err = httpServer.Shutdown(httpShutdownContext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to shutdown HTTP server: %v", err)
|
logger.Error("Failed to shutdown HTTP server",
|
||||||
|
slog.String("error", err.Error()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
34
silence.go
34
silence.go
|
@ -4,7 +4,9 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -40,26 +42,29 @@ func (br *bridge) handleSilences(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := io.ReadAll(r.Body)
|
b, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
br.logger.Debugf("silences: %v", err)
|
br.logger.Error("Silences: Failed to read body",
|
||||||
|
slog.String("error", err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err = base64.StdEncoding.DecodeString(string(b))
|
b, err = base64.StdEncoding.DecodeString(string(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
br.logger.Debugf("silences: %v", err)
|
br.logger.Error("Silences: Failed to decode",
|
||||||
|
slog.String("error", err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var sb silenceBody
|
var sb silenceBody
|
||||||
err = json.Unmarshal(b, &sb)
|
err = json.Unmarshal(b, &sb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
br.logger.Debugf("silences: %v", err)
|
br.logger.Error("Silences: Failed to unmarshal",
|
||||||
|
slog.String("error", err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +90,8 @@ func (br *bridge) handleSilences(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
b, err = json.Marshal(silence)
|
b, err = json.Marshal(silence)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
br.logger.Debugf("silences: %v", err)
|
br.logger.Error("Silences: Failed to marshal",
|
||||||
|
slog.String("error", err.Error()))
|
||||||
return
|
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))
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
br.logger.Debugf("silences: %v", err)
|
br.logger.Error("Silences: Failed to create request",
|
||||||
|
slog.String("error", err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,27 +116,32 @@ func (br *bridge) handleSilences(w http.ResponseWriter, r *http.Request) {
|
||||||
req.Header.Add("Content-Type", "application/json")
|
req.Header.Add("Content-Type", "application/json")
|
||||||
resp, err := br.client.Do(req)
|
resp, err := br.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
br.logger.Debugf("silences: %v", err)
|
br.logger.Error("Silences: Failed to POST request",
|
||||||
|
slog.String("error", err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
b, err = io.ReadAll(resp.Body)
|
b, err = io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
br.logger.Debugf("silences: %v", err)
|
br.logger.Error("Silences: Failed to read response body",
|
||||||
|
slog.String("error", err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var id silenceResponse
|
var id silenceResponse
|
||||||
if err := json.Unmarshal(b, &id); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
br.logger.Infof("Created new silence %s", id.ID)
|
br.logger.Info("Silences: Created new silence",
|
||||||
|
slog.String("ID", id.ID))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue