Files
httpmw/docs/adr/ADR-001-logz-context-import-exception.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

54 lines
2.5 KiB
Markdown

# 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`:
```go
// 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
- `httpmw` has a direct module dependency on `logz` in its `go.mod`. Upgrading
`logz` is a breaking change for `httpmw` if the context helper signatures change.
- `logz.WithRequestID` and `logz.GetRequestID` form a shared context contract
between `httpmw` (which stores the ID) and `logz.Logger` (which reads it when
enriching log records). Both sides of the contract must be the same package.
- Applications that use `httpmw` without `logz` as their logger will still get
request IDs injected into the context correctly — `RequestID` middleware 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.