Log every request with a request id

Go · Go · intermediate · modification

Adds request ids and an access log. `reqlog.Middleware` wraps the whole mux, so every request it lets through, health checks and 404s included, gets an id (if reading the random source fails, the request is answered 500 without one): the load balancer's `X-Request-ID` when it looks like one, otherwise a fresh 128-bit id from `crypto/rand`. The id goes into the request context (`reqlog.ID(ctx)`) and back to the client in the same header, and once the handler returns we log one JSON line with method, path, status, bytes and duration. A small `ResponseWriter` wrapper captures the status and the byte count, and keeps `http.ResponseController` working for the streaming endpoints. Tests cover the id rules and the logged status and bytes.

Orders API in Go 1.22, standard library only (`net/http` method-pattern `ServeMux`, `log/slog`). Unchanged: `orders.Register(mux, db, logger)` adds the `/orders` routes; `httpserver.Serve(ctx, addr, h)` runs an `http.Server` with `h` as its handler until `ctx` is cancelled, then drains in-flight requests. The service runs behind a load balancer that sets `X-Request-ID` on the requests it forwards, but clients can send the header too.

Requirements

Files touched

--- cmd/server/main.go
 	"example.com/shop/internal/httpserver"
 	"example.com/shop/internal/orders"
+	"example.com/shop/internal/reqlog"
 	"example.com/shop/internal/store"
 )
 		w.WriteHeader(http.StatusNoContent)
 	})
-	return httpserver.Serve(ctx, cfg.Addr, mux)
+	return httpserver.Serve(ctx, cfg.Addr, reqlog.Middleware(logger, mux))
 }
 
--- internal/reqlog/reqlog.go
+// Package reqlog gives every request an id and writes one access log line
+// per request.
+package reqlog
+
+import (
+	"context"
+	"crypto/rand"
+	"encoding/hex"
+	"log/slog"
+	"net/http"
+	"regexp"
+	"time"
+)
+
+// Header carries the request id in both directions.
+const Header = "X-Request-ID"
+

Review this PR

Go practice