Files
httpmw/docs/adr/ADR-002-requestid-via-logz-context.md
Rene Nochebuena ad2a9e465e 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/
2026-03-19 00:03:24 +00:00

2.4 KiB

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.