package middleware import ( "context" "encoding/json" "net/http" "strings" "time" "github.com/go-chi/chi/v5/middleware" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) type ContextKey string const ( LoggerContext = ContextKey("logger") ) func LoggingMiddleware(logger *zap.Logger) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) start := time.Now() defer func() { if ww.Status() < 300 && strings.HasPrefix(r.URL.Path, "/health") { // Don't log health checks unless they fail (ping endpoint returns empty response, HTTP status 204). return } var body map[string]interface{} json.NewDecoder(r.Body).Decode(&body) fields := []zapcore.Field{ zap.Any("body", r.Body), zap.String("remote_ip", r.RemoteAddr), zap.String("method", r.Method), zap.String("uri", r.URL.Path), zap.String("request_id", middleware.GetReqID(r.Context())), zap.Int("status", ww.Status()), zap.Float64("latency_ms", float64(time.Since(start))/float64(time.Millisecond)), zap.Int("size", ww.BytesWritten()), } logger.Info("HTTP request processed", fields...) }() r = r.WithContext(context.WithValue(r.Context(), LoggerContext, logger)) next.ServeHTTP(ww, r) } return http.HandlerFunc(fn) } }