Chi Middleware Request Logging

Log every request with chi middleware: request ids, durations, status codes, and route patterns instead of raw paths.

GoResilience

Code

snippet
package main

import (
	"fmt"
	"log/slog"
	"net/http"
	"os"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
)

func main() {
	logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelInfo,
	}))

	r := chi.NewRouter()
	r.Use(middleware.RequestID)
	r.Use(middleware.RealIP)
	r.Use(requestLogger(logger))
	r.Use(middleware.Recoverer)

	r.Get("/orders/{orderID}", func(w http.ResponseWriter, r *http.Request) {
		id := chi.URLParam(r, "orderID")
		logger.InfoContext(r.Context(), "order fetched",
			slog.String("order_id", id),
		)
		w.Write([]byte("order " + id))
	})

	http.ListenAndServe(":8080", r)
}

func requestLogger(logger *slog.Logger) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			started := time.Now()
			wrapped := middleware.NewWrapResponseWriter(w, r.ProtoMajor)

			defer func() {
				logger.LogAttrs(r.Context(), slog.LevelInfo, "request",
					slog.String("request_id", middleware.GetReqID(r.Context())),
					slog.String("method", r.Method),
					slog.String("route", chi.RouteContext(r.Context()).RoutePattern()),
					slog.Int("status", wrapped.Status()),
					slog.Int("bytes", wrapped.BytesWritten()),
					slog.Duration("duration", time.Since(started)),
				)
			}()

			next.ServeHTTP(wrapped, r)
		})
	}
}