From a972953040d4bf77884da17b72e15643ce8beb3a Mon Sep 17 00:00:00 2001 From: Ryan McGuire Date: Sun, 24 Aug 2025 11:46:17 -0400 Subject: [PATCH 1/2] refactors HTTP server initialization, improves logging middleware, and updates documentation --- pkg/srv/http/http.go | 7 +++++-- pkg/srv/http/http_log.go | 36 ++++++++++++++++++------------------ 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/pkg/srv/http/http.go b/pkg/srv/http/http.go index 8f7ec42..78bbea6 100644 --- a/pkg/srv/http/http.go +++ b/pkg/srv/http/http.go @@ -1,3 +1,6 @@ +// Package http provides functionality for setting up and managing HTTP servers. +// It includes features for health checks, Prometheus metrics, OpenTelemetry +// tracing, and custom middleware integration. package http import ( @@ -133,8 +136,8 @@ func prepHTTPServer(opts *opts.AppHTTP) *http.Server { } } -// Returns a shutdown func and a done channel if the -// server aborts abnormally. Returns error on failure to start +// InitHTTPServer returns a shutdown func and a done channel if the +// server aborts abnormally. Returns error on failure to start. func InitHTTPServer(opts *opts.AppHTTP) ( func(context.Context) error, <-chan any, error, ) { diff --git a/pkg/srv/http/http_log.go b/pkg/srv/http/http_log.go index 6e9b520..4304a36 100644 --- a/pkg/srv/http/http_log.go +++ b/pkg/srv/http/http_log.go @@ -41,29 +41,29 @@ func loggingMiddleware(appCtx context.Context, next http.Handler) http.Handler { Dur("duration", time.Since(start)). Msg("http request served") - // Log response body - trcLog := log.Trace(). - Str("path", r.URL.Path). - Int("statusCode", lrr.statusCode) + // Log response with body if not 204 + if lrr.statusCode != http.StatusNoContent { + trcLog := log.Trace(). + Str("path", r.URL.Path). + Int("statusCode", lrr.statusCode) + firstByte, err := lrr.body.ReadByte() + if err != nil { + trcLog.Err(errors.New("invalid response body")).Send() + return + } + lrr.body.UnreadByte() - // Check if it's JSON - firstByte, err := lrr.body.ReadByte() - if err != nil { - trcLog.Err(errors.New("invalid response body")).Send() - return + if firstByte == '{' { + trcLog = trcLog.RawJSON("response", lrr.body.Bytes()) + } else { + trcLog = trcLog.Bytes("response", lrr.body.Bytes()) + } + trcLog.Msg("http response") } - lrr.body.UnreadByte() - - if firstByte == '{' { - trcLog = trcLog.RawJSON("response", lrr.body.Bytes()) - } else { - trcLog = trcLog.Bytes("response", lrr.body.Bytes()) - } - trcLog.Msg("response payload") }) } -// Implement Flush to support the http.Flusher interface +// Flush implements the http.Flusher interface to allow flushing buffered data. func (w *LoggingResponseWriter) Flush() { if flusher, ok := w.ResponseWriter.(http.Flusher); ok { flusher.Flush() -- 2.49.1 From ae11d4aa0160849dc8c5388638fa70281892d053 Mon Sep 17 00:00:00 2001 From: Ryan McGuire Date: Sun, 24 Aug 2025 11:50:03 -0400 Subject: [PATCH 2/2] refactor: refine HTTP response body logging, explicitly handling 204 No Content, and improving error management for body reads --- pkg/srv/http/http_log.go | 46 ++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/pkg/srv/http/http_log.go b/pkg/srv/http/http_log.go index 4304a36..cb05005 100644 --- a/pkg/srv/http/http_log.go +++ b/pkg/srv/http/http_log.go @@ -3,7 +3,8 @@ package http import ( "bytes" "context" - "errors" + "fmt" + "io" "net/http" "regexp" "time" @@ -41,25 +42,38 @@ func loggingMiddleware(appCtx context.Context, next http.Handler) http.Handler { Dur("duration", time.Since(start)). Msg("http request served") - // Log response with body if not 204 - if lrr.statusCode != http.StatusNoContent { + // Log response with body if not 204 + if lrr.statusCode == http.StatusNoContent { trcLog := log.Trace(). Str("path", r.URL.Path). Int("statusCode", lrr.statusCode) - firstByte, err := lrr.body.ReadByte() - if err != nil { - trcLog.Err(errors.New("invalid response body")).Send() - return - } - lrr.body.UnreadByte() - - if firstByte == '{' { - trcLog = trcLog.RawJSON("response", lrr.body.Bytes()) - } else { - trcLog = trcLog.Bytes("response", lrr.body.Bytes()) - } - trcLog.Msg("http response") + trcLog.Msg("http response (no content)") // Explicitly log 204 + return // No body to log for 204 No Content } + + trcLog := log.Trace(). + Str("path", r.URL.Path). + Int("statusCode", lrr.statusCode) + + firstByte, err := lrr.body.ReadByte() + if err != nil { + if err == io.EOF { + // Body is empty, which might be valid for some non-204 responses. + trcLog.Msg("http response (empty body)") + } else { + // Other error reading the body. Wrap the original error for context. + trcLog.Err(fmt.Errorf("error reading response body: %w", err)).Send() + } + return // No further body processing if there was an error or it was empty + } + lrr.body.UnreadByte() // Put the byte back for Bytes() to read + + if firstByte == '{' { + trcLog = trcLog.RawJSON("response", lrr.body.Bytes()) + } else { + trcLog = trcLog.Bytes("response", lrr.body.Bytes()) + } + trcLog.Msg("http response") }) } -- 2.49.1