From 902327eb8407418c69dd0e45e38fe44e7b18c9fe Mon Sep 17 00:00:00 2001 From: Jonathan Buch Date: Mon, 1 Jul 2024 08:48:37 +0200 Subject: [PATCH 1/7] Update dependencies --- go.mod | 7 ++++--- go.sum | 18 ++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index e399783..8bddc63 100644 --- a/go.mod +++ b/go.mod @@ -26,16 +26,17 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.54.0 // indirect + github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect go.opentelemetry.io/otel/metric v1.27.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240624140628-dc46fd24d27d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d // indirect google.golang.org/grpc v1.64.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go.sum b/go.sum index a0fa34f..d1da315 100644 --- a/go.sum +++ b/go.sum @@ -23,14 +23,16 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8= -github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= @@ -63,14 +65,10 @@ golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 h1:QW9+G6Fir4VcRXVH8x3LilNAb6cxBGLa6+GM4hRwexE= -google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3/go.mod h1:kdrSS/OiLkPrNUpzD4aHgCq2rVuC/YRxok32HXZ4vRE= -google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 h1:MuYw1wJzT+ZkybKfaOXKp5hJiZDn2iHaXRw0mRYdHSc= -google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 h1:9Xyg6I9IWQZhRVfCWjKK+l6kI0jHcPesVlMnT//aHNo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/api v0.0.0-20240624140628-dc46fd24d27d h1:Aqf0fiIdUQEj0Gn9mKFFXoQfTTEaNopWpfVyYADxiSg= +google.golang.org/genproto/googleapis/api v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Od4k8V1LQSizPRUK4OzZ7TBE/20k+jPczUDAEyvn69Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d h1:k3zyW3BYYR30e8v3x0bTDdE9vpYFjZHK+HcyqkrppWk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= From eb871427cbd1c0f7732a2e09c4c54c7db2681cff Mon Sep 17 00:00:00 2001 From: Jonathan Buch Date: Mon, 1 Jul 2024 09:15:13 +0200 Subject: [PATCH 2/7] Prepare new release --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14b19fa..8a4c442 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Releases +## 1.8.X - 2024-07-XX Tracing IDs + +* Add spans/traces to logs to enable correlation +* Cleanup file paths in logging for visibility by stripping the + build path. +* Remove error logging keys containing `` strings. + ## 1.8.0 - 2024-06-05 Icinga2 Host ACKs * Downtimes and acknowledgements as well as disabling notifications From d450407c34a26cb942ef5aeab0af9b9f1a2f28df Mon Sep 17 00:00:00 2001 From: Jonathan Buch Date: Mon, 1 Jul 2024 09:16:04 +0200 Subject: [PATCH 3/7] logging, remove duplicated keys * tracing IDs duplicate client markers --- pkg/web/alerts.go | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/pkg/web/alerts.go b/pkg/web/alerts.go index 2e0aeab..7a1e8ed 100644 --- a/pkg/web/alerts.go +++ b/pkg/web/alerts.go @@ -50,7 +50,6 @@ func (h *webHandler) wsalerts(s *websocket.Conn) { tr := trace.SpanFromContext(s.Request().Context()).SpanContext().TraceID().String() slog.InfoContext(s.Request().Context(), "Registering websocket connection", - slog.String("client", tr), slog.String("dashboard", dashboardName)) update := h.aggregator.Register(tr) defer h.aggregator.Unregister(tr) @@ -59,18 +58,15 @@ func (h *webHandler) wsalerts(s *websocket.Conn) { select { case _, ok := <-update: if !ok { - slog.DebugContext(s.Request().Context(), "stop sending to websocket client, update channel closed", - slog.String("client", tr)) + slog.DebugContext(s.Request().Context(), "stop sending to websocket client, update channel closed") update = nil } - slog.DebugContext(s.Request().Context(), "sending to websocket client", - slog.String("client", tr)) + slog.DebugContext(s.Request().Context(), "sending to websocket client") aggregate := h.aggregator.Alerts(dashboardName) renderer(webContent{Content: aggregate}) case <-s.Request().Context().Done(): - slog.DebugContext(s.Request().Context(), "stop sending to websocket client, req ctx done", - slog.String("client", tr)) + slog.DebugContext(s.Request().Context(), "stop sending to websocket client, req ctx done") return } } @@ -94,7 +90,7 @@ func (h *webHandler) ssealerts(w http.ResponseWriter, req *http.Request) { defer cancel() tr := trace.SpanFromContext(req.Context()).SpanContext().TraceID().String() - slog.InfoContext(req.Context(), "Registering sse connection", slog.String("client", tr)) + slog.InfoContext(req.Context(), "Registering sse connection") update := h.aggregator.Register(tr) defer h.aggregator.Unregister(tr) @@ -102,20 +98,18 @@ func (h *webHandler) ssealerts(w http.ResponseWriter, req *http.Request) { select { case _, ok := <-update: if !ok { - slog.DebugContext(req.Context(), "stop sending to sse client", slog.String("client", tr)) + slog.DebugContext(req.Context(), "stop sending to sse client") return } - slog.DebugContext(req.Context(), "sending to sse client", slog.String("client", tr)) + slog.DebugContext(req.Context(), "sending to sse client") aggregate := h.aggregator.Alerts(dashboardName) if err := renderer(webContent{Content: aggregate}); err != nil { - slog.DebugContext(req.Context(), "stop sending to sse client", - slog.String("client", tr), - slog.Any("error", err)) + slog.DebugContext(req.Context(), "stop sending to sse client", slog.Any("error", err)) return } case <-req.Context().Done(): - slog.InfoContext(req.Context(), "stop sending to sse client", slog.String("client", tr)) + slog.InfoContext(req.Context(), "stop sending to sse client") return } } From 4e9b2bae919361519e8b672e93d80c21c8c99463 Mon Sep 17 00:00:00 2001 From: Jonathan Buch Date: Mon, 1 Jul 2024 12:49:32 +0200 Subject: [PATCH 4/7] logging, skip notification on disconnections * Hard disconnects were logged twice. Disconnections should be less visible, as it's a common occurrence and only worthy logging while debugging. * Cleaner error handling in renderers, panic on first error and not only on the last. --- pkg/web/alerts.go | 7 ++++- pkg/web/web.go | 69 ++++++++++++++++++++++++++--------------------- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/pkg/web/alerts.go b/pkg/web/alerts.go index 7a1e8ed..1e8eda3 100644 --- a/pkg/web/alerts.go +++ b/pkg/web/alerts.go @@ -1,6 +1,7 @@ package web import ( + "errors" "log/slog" "net/http" "path/filepath" @@ -33,7 +34,11 @@ func (h *webHandler) wsalerts(s *websocket.Conn) { if err := recover(); err != nil { switch err := err.(type) { case error: - slog.InfoContext(s.Request().Context(), "panic serving", slog.Any("error", err)) + if errors.Is(err, DisconnectError) { + slog.DebugContext(s.Request().Context(), "panic serving", slog.Any("error", err)) + } else { + slog.InfoContext(s.Request().Context(), "panic serving", slog.Any("error", err)) + } default: slog.InfoContext(s.Request().Context(), "panic serving", slog.Any("error", err)) } diff --git a/pkg/web/web.go b/pkg/web/web.go index 823c199..07eb7a5 100644 --- a/pkg/web/web.go +++ b/pkg/web/web.go @@ -6,6 +6,7 @@ import ( "context" "embed" "encoding/json" + "errors" "fmt" html "html/template" "io/fs" @@ -47,6 +48,11 @@ type webContent struct { Style string } +var ( + TemplateError = errors.New("template execution failed") + DisconnectError = errors.New("client disconnected") +) + func WebHandler(cfg *config.Config, aggregator *aggregation.Aggregator) http.Handler { handler := &webHandler{ aggregator: aggregator, @@ -126,10 +132,8 @@ func (h *webHandler) baseRenderer(req *http.Request, dashboardName string, patte data.Dashboards = h.dashboards data.Dashboard = dashboardName - err := tmpl.ExecuteTemplate(w, templateDefinition, data) - if err != nil { - slog.ErrorContext(req.Context(), "template execution failed", slog.Any("error", err)) - panic(err) + if err := tmpl.ExecuteTemplate(w, templateDefinition, data); err != nil { + panic(errors.Join(TemplateError, err)) } } } @@ -161,10 +165,8 @@ func (h *webHandler) partialRenderer(req *http.Request, dashboardName string, pa data.Style = h.style data.Dashboard = dashboardName - err := tmpl.ExecuteTemplate(w, templateDefinition, data) - if err != nil { - slog.ErrorContext(req.Context(), "template execution failed", slog.Any("error", err)) - panic(err) + if err := tmpl.ExecuteTemplate(w, templateDefinition, data); err != nil { + panic(errors.Join(TemplateError, err)) } } } @@ -186,7 +188,9 @@ func (h *webHandler) sseRenderer(w http.ResponseWriter, req *http.Request, patte tmpl, err := tmpl.ParseFS(h.fs, templateFiles...) if err != nil { slog.ErrorContext(req.Context(), "compiling template failed", slog.Any("error", err)) - panic(err) + return func(data webContent) error { + return err + }, func() {} } // prepare the flusher @@ -204,8 +208,7 @@ func (h *webHandler) sseRenderer(w http.ResponseWriter, req *http.Request, patte w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") - _, err = fmt.Fprint(w, "retry: 60000\n\n") - if err != nil { + if _, err := fmt.Fprint(w, "retry: 60000\n\n"); err != nil { return func(data webContent) error { return err }, cancel @@ -220,30 +223,37 @@ func (h *webHandler) sseRenderer(w http.ResponseWriter, req *http.Request, patte buf := new(bytes.Buffer) - err = tmpl.ExecuteTemplate(buf, templateDefinition, data) - if err != nil { - slog.InfoContext(req.Context(), "template execution failed", slog.Any("error", err)) - panic(err) + if err := tmpl.ExecuteTemplate(buf, templateDefinition, data); err != nil { + panic(errors.Join(TemplateError, err)) } tr := trace.SpanFromContext(req.Context()) - _, err = fmt.Fprintf(w, "id: %s\n", tr.SpanContext().TraceID()) - _, err = fmt.Fprint(w, "event: message\n") + if _, err := fmt.Fprintf(w, "id: %s\n", tr.SpanContext().TraceID()); err != nil { + panic(errors.Join(DisconnectError, err)) + } + if _, err := fmt.Fprint(w, "event: message\n"); err != nil { + panic(errors.Join(DisconnectError, err)) + } scanner := bufio.NewScanner(buf) for scanner.Scan() { - _, err = w.Write([]byte("data: ")) - _, err = w.Write(scanner.Bytes()) - _, err = w.Write([]byte("\n")) + if _, err := w.Write([]byte("data: ")); err != nil { + panic(errors.Join(DisconnectError, err)) + } + if _, err := w.Write(scanner.Bytes()); err != nil { + panic(errors.Join(DisconnectError, err)) + } + if _, err := w.Write([]byte("\n")); err != nil { + panic(errors.Join(DisconnectError, err)) + } + } + if _, err := w.Write([]byte("\n")); err != nil { + panic(errors.Join(DisconnectError, err)) } - _, err = w.Write([]byte("\n")) flusher.Flush() - if err != nil { - slog.InfoContext(req.Context(), "template execution failed", slog.Any("error", err)) - } - return err + return nil }, cancel } @@ -263,8 +273,7 @@ func (h *webHandler) wsRenderer(s *websocket.Conn, patterns ...string) wsRenderF tmpl := html.New(templateDefinition).Funcs(funcs) tmpl, err := tmpl.ParseFS(h.fs, templateFiles...) if err != nil { - slog.ErrorContext(s.Request().Context(), "compiling template failed", slog.Any("error", err)) - panic(err) + panic(errors.Join(TemplateError, err)) } return func(data webContent) { @@ -281,14 +290,12 @@ func (h *webHandler) wsRenderer(s *websocket.Conn, patterns ...string) wsRenderF err = tmpl.ExecuteTemplate(buf, templateDefinition, data) if err != nil { - slog.InfoContext(s.Request().Context(), "template execution failed", slog.Any("error", err)) - panic(err) + panic(errors.Join(TemplateError, err)) } _, err = w.Write(buf.Bytes()) if err != nil { - slog.InfoContext(s.Request().Context(), "sending failed", slog.Any("error", err)) - panic(err) + panic(errors.Join(DisconnectError, err)) } } } From 4e1abac5c0caa81340baf98698ae1212c82ad818 Mon Sep 17 00:00:00 2001 From: Jonathan Buch Date: Mon, 1 Jul 2024 13:10:28 +0200 Subject: [PATCH 5/7] web, nicer error handling for websockets --- pkg/web/web.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/web/web.go b/pkg/web/web.go index 07eb7a5..160f168 100644 --- a/pkg/web/web.go +++ b/pkg/web/web.go @@ -279,7 +279,7 @@ func (h *webHandler) wsRenderer(s *websocket.Conn, patterns ...string) wsRenderF return func(data webContent) { w, err := s.NewFrameWriter(websocket.TextFrame) if err != nil { - panic(err) + panic(fmt.Errorf("cannot create websocket text frame writer: %w", err)) } data.Version = version.Info.Version @@ -288,13 +288,11 @@ func (h *webHandler) wsRenderer(s *websocket.Conn, patterns ...string) wsRenderF buf := new(bytes.Buffer) - err = tmpl.ExecuteTemplate(buf, templateDefinition, data) - if err != nil { + if err := tmpl.ExecuteTemplate(buf, templateDefinition, data); err != nil { panic(errors.Join(TemplateError, err)) } - _, err = w.Write(buf.Bytes()) - if err != nil { + if _, err = w.Write(buf.Bytes()); err != nil { panic(errors.Join(DisconnectError, err)) } } From 0accd2841bdba96814c7e5401c0e84cfd1249d52 Mon Sep 17 00:00:00 2001 From: Jonathan Buch Date: Mon, 1 Jul 2024 13:11:06 +0200 Subject: [PATCH 6/7] web, code cleanup for templating --- pkg/web/web.go | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/pkg/web/web.go b/pkg/web/web.go index 160f168..2941f48 100644 --- a/pkg/web/web.go +++ b/pkg/web/web.go @@ -105,11 +105,8 @@ func (h *webHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { type renderFunc func(w http.ResponseWriter, statusCode int, data webContent) func (h *webHandler) baseRenderer(req *http.Request, dashboardName string, patterns ...string) renderFunc { - var templateFiles []string - var templateDefinition string - - templateFiles = append([]string{"_base.gohtml"}, patterns...) - templateDefinition = "base" + templateFiles := append([]string{"_base.gohtml"}, patterns...) + templateDefinition := "base" funcs := html.FuncMap{ "niceDuration": niceDuration, @@ -139,11 +136,8 @@ func (h *webHandler) baseRenderer(req *http.Request, dashboardName string, patte } func (h *webHandler) partialRenderer(req *http.Request, dashboardName string, patterns ...string) renderFunc { - var templateFiles []string - var templateDefinition string - - templateFiles = append([]string{"_stream.gohtml"}, patterns...) - templateDefinition = "base" + templateFiles := append([]string{"_stream.gohtml"}, patterns...) + templateDefinition := "base" funcs := html.FuncMap{ "niceDuration": niceDuration, @@ -174,11 +168,8 @@ func (h *webHandler) partialRenderer(req *http.Request, dashboardName string, pa type sseRenderFunc func(data webContent) error func (h *webHandler) sseRenderer(w http.ResponseWriter, req *http.Request, patterns ...string) (sseRenderFunc, context.CancelFunc) { - var templateFiles []string - var templateDefinition string - - templateFiles = append([]string{"_stream.gohtml"}, patterns...) - templateDefinition = "content-container" + templateFiles := append([]string{"_stream.gohtml"}, patterns...) + templateDefinition := "content-container" funcs := html.FuncMap{ "niceDuration": niceDuration, @@ -260,11 +251,8 @@ func (h *webHandler) sseRenderer(w http.ResponseWriter, req *http.Request, patte type wsRenderFunc func(data webContent) func (h *webHandler) wsRenderer(s *websocket.Conn, patterns ...string) wsRenderFunc { - var templateFiles []string - var templateDefinition string - - templateFiles = append([]string{"_stream.gohtml"}, patterns...) - templateDefinition = "content-container" + templateFiles := append([]string{"_stream.gohtml"}, patterns...) + templateDefinition := "content-container" funcs := html.FuncMap{ "niceDuration": niceDuration, From 07f82d25ccd646a6a5a70be4bf87565126edcc40 Mon Sep 17 00:00:00 2001 From: Jonathan Buch Date: Mon, 1 Jul 2024 13:11:46 +0200 Subject: [PATCH 7/7] web, also hide error messages when disconnecting mid stream * This can happen on normal connections as well, not only on websockets --- pkg/web/web.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/web/web.go b/pkg/web/web.go index 2941f48..95e5518 100644 --- a/pkg/web/web.go +++ b/pkg/web/web.go @@ -88,7 +88,11 @@ func (h *webHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if err := recover(); err != nil { switch err := err.(type) { case error: - slog.ErrorContext(r.Context(), "panic serving", slog.Any("error", err)) + if errors.Is(err, DisconnectError) { + slog.DebugContext(r.Context(), "panic serving", slog.Any("error", err)) + } else { + slog.InfoContext(r.Context(), "panic serving", slog.Any("error", err)) + } default: slog.ErrorContext(r.Context(), "panic serving", slog.Any("error", err)) }