Files
logz/docs/adr/ADR-003-exported-logger-interface.md
Rene Nochebuena 3667b92fab feat(logz): initial stable release v0.9.0
Structured logger backed by log/slog with request-context enrichment, extra-field context helpers, and duck-typed automatic error enrichment.

What's included:
- `Logger` interface with Debug / Info / Warn / Error / With / WithContext; `New(Options)` constructor writing to os.Stdout
- `WithRequestID` / `GetRequestID` and `WithField` / `WithFields` context helpers — package owns both context keys
- Automatic error_code and context-field enrichment in Logger.Error via duck-typed errorWithCode / errorWithContext interfaces (no xerrors import)

Tested-via: todo-api POC integration
Reviewed-against: docs/adr/
2026-03-18 13:31:39 -06:00

2.3 KiB

ADR-003: Exported Logger Interface

Status: Accepted Date: 2026-03-18

Context

Global ADR-001 establishes that app-facing modules define a local Logger interface satisfied by logz.Logger so that libraries do not import logz directly. For this duck-typing pattern to work, logz must export its Logger interface with a stable method set that other packages can replicate locally.

If Logger were unexported, or if New returned *slogLogger (the concrete type), consumers would need to import logz just to name the type in a parameter or field declaration, coupling every module to the logging library.

Decision

Logger is an exported interface in the logz package:

type Logger interface {
    Debug(msg string, args ...any)
    Info(msg string, args ...any)
    Warn(msg string, args ...any)
    Error(msg string, err error, args ...any)
    With(args ...any) Logger
    WithContext(ctx context.Context) Logger
}

New(opts Options) Logger returns the interface, not *slogLogger. The concrete type is unexported. With returns Logger (interface), not *slogLogger — this is enforced by the return type in the interface definition and by the compliance test.

Modules that accept a logger define a local interface with the subset of methods they use (typically Info, Warn, Error). logz.Logger satisfies any such local interface because it is a superset.

The Error signature deliberately differs from slog's: Error(msg string, err error, args ...any). The error is a first-class parameter rather than a key-value attribute, enabling automatic enrichment from duck-typed interfaces (ErrorCode, ErrorContext) without any extra caller code.

Consequences

  • Tier 1+ modules can accept a logger via a local Logger interface without importing logz. They are decoupled from the logging backend.
  • launcher, which imports logz directly (it is the bootstrap layer), uses logz.Logger as the concrete type in its New signature.
  • Adding methods to logz.Logger is a breaking change for all callers that replicate the interface locally. Any new method must be evaluated carefully and accompanied by a version bump.
  • The Error(msg, err, args...) convention is not interchangeable with slog's Error(msg, args...). Adapters must account for this signature difference.