Standalone net/http middleware for panic recovery, CORS, request ID injection, and request logging. What's included: - Recover(): panic -> 500, captures debug.Stack, no logger required - CORS(origins): OPTIONS 204 preflight, origin allowlist, package-wide method/header constants - RequestID(generator): injects ID via logz.WithRequestID, sets X-Request-ID response header - RequestLogger(logger): logs method/path/status/latency/request_id; Error for 5xx, Info otherwise - Logger interface: Info, Error, With — duck-typed; satisfied by logz.Logger - StatusRecorder: exported http.ResponseWriter wrapper that captures written status code - Direct logz import for context helpers (documented exception to ADR-001) Tested-via: todo-api POC integration Reviewed-against: docs/adr/
2.5 KiB
ADR-001: Direct logz Import for Context Helpers (Exception to Global ADR-001)
Status: Accepted Date: 2026-03-18
Context
Global ADR-001 states that modules receiving a logger from the application layer
should duck-type the logger injection point with a private interface (e.g.
type Logger interface { Info(...); Error(...) }), so the application is not forced
to import logz just to satisfy a concrete type.
httpmw follows this rule for its Logger interface in logger.go. Any struct
with the right method set satisfies it.
However, httpmw also calls logz.WithRequestID (in requestid.go) and
logz.GetRequestID (in logger.go) to store and read the request ID from context.
These functions use an unexported context key type defined inside logz:
// inside logz — not exported
type contextKey string
const requestIDKey contextKey = "request_id"
A context value stored with that key can only be read back with the same key. If
httpmw tried to replicate the storage using its own unexported key, the values set
by logz.WithRequestID would be invisible to logz.GetRequestID, and vice-versa —
breaking the downstream logz.Logger.WithContext integration.
Decision
httpmw imports logz directly for context helper access (logz.WithRequestID,
logz.GetRequestID). This is an explicit, documented exception to the
duck-typed Logger rule in global ADR-001.
The Logger injection point remains duck-typed (httpmw.Logger interface in
logger.go). The exception applies only to the two context functions, not to the
logger parameter of RequestLogger.
Consequences
httpmwhas a direct module dependency onlogzin itsgo.mod. Upgradinglogzis a breaking change forhttpmwif the context helper signatures change.logz.WithRequestIDandlogz.GetRequestIDform a shared context contract betweenhttpmw(which stores the ID) andlogz.Logger(which reads it when enriching log records). Both sides of the contract must be the same package.- Applications that use
httpmwwithoutlogzas their logger will still get request IDs injected into the context correctly —RequestIDmiddleware works independently. The ID simply won't be picked up automatically by a non-logz logger. - This exception must not be used as a precedent for importing logz elsewhere without justification. The rule remains: duck-type logger injection; import logz only when the unexported context key contract requires it.