feat(httpmw): initial stable release v0.9.0
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/
This commit is contained in:
50
docs/adr/ADR-002-requestid-via-logz-context.md
Normal file
50
docs/adr/ADR-002-requestid-via-logz-context.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# ADR-002: Request ID Injected via logz Context Helpers
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-03-18
|
||||
|
||||
## Context
|
||||
|
||||
Each HTTP request should carry a unique identifier that appears in log records,
|
||||
error responses, and the `X-Request-ID` response header, so that a single request
|
||||
can be traced across log lines.
|
||||
|
||||
There are two sub-problems:
|
||||
|
||||
1. **Generation and storage**: the middleware must generate an ID and make it
|
||||
available to downstream code via the request context.
|
||||
2. **Retrieval for logging**: `RequestLogger` must be able to read the ID from the
|
||||
context to include it in log records.
|
||||
|
||||
The naive approach — store the ID under a locally-defined context key — breaks
|
||||
interoperability with `logz.Logger.WithContext`, which enriches log records with
|
||||
values stored under `logz`'s own unexported key. If a different key is used, logz
|
||||
cannot find the ID, and it does not appear automatically in structured log output.
|
||||
|
||||
## Decision
|
||||
|
||||
The `RequestID` middleware calls `logz.WithRequestID(ctx, id)` to store the
|
||||
generated ID in context. `RequestLogger` calls `logz.GetRequestID(r.Context())` to
|
||||
retrieve it.
|
||||
|
||||
Both functions use the same unexported key inside the `logz` package, guaranteeing
|
||||
that the value stored by `RequestID` is the same value retrieved by `RequestLogger`
|
||||
and, more importantly, by any `logz.Logger` downstream that calls `WithContext`.
|
||||
|
||||
The ID is also written to the `X-Request-ID` response header at the middleware
|
||||
level, so clients can correlate responses to requests without parsing log files.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Any `logz.Logger` in the request chain that calls `.WithContext(ctx)` automatically
|
||||
inherits the request ID as a structured log field — no manual plumbing required.
|
||||
- The generator function is injected by the caller (`RequestID(generator func() string)`),
|
||||
keeping the ID format flexible (UUID, ULID, or any string).
|
||||
- If the same request ID is sent in the `X-Request-ID` request header, it is ignored
|
||||
by `RequestID` — the middleware always generates a fresh ID. Preserving inbound
|
||||
IDs is a separate concern that should be handled explicitly if required.
|
||||
- This design requires `httpmw` to import `logz` directly (see ADR-001 for the
|
||||
justification of that exception).
|
||||
- Global ADR-003 (context helpers live with data owners) is directly applied here:
|
||||
`logz` owns the request ID context key because the request ID is a logging
|
||||
concern, not an auth or business concern.
|
||||
Reference in New Issue
Block a user