56 lines
2.3 KiB
Markdown
56 lines
2.3 KiB
Markdown
|
|
# 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:
|
||
|
|
|
||
|
|
```go
|
||
|
|
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.
|