Files
logz/CLAUDE.md

116 lines
4.1 KiB
Markdown
Raw Normal View History

# logz
Structured logger backed by log/slog with request-context enrichment and duck-typed error integration.
## Purpose
`logz` provides a stable `Logger` interface and a `New()` constructor. It wraps
`log/slog` (stdlib) so no external logging library is required. It owns the request
correlation ID context key and extra-field context key, providing helpers to attach
and retrieve them. When an error passed to `Logger.Error` implements `ErrorCode()`
or `ErrorContext()`, those fields are automatically appended to the log record —
without importing `xerrors`.
## Tier & Dependencies
**Tier:** 1
**Imports:** `context`, `errors`, `log/slog`, `os` (stdlib only)
**Must NOT import:** `xerrors`, `rbac`, `launcher`, or any other micro-lib module.
The xerrors bridge is achieved via private duck-typed interfaces (`errorWithCode`,
`errorWithContext`) — no import is needed.
## Key Design Decisions
- Wraps `log/slog` exclusively; no external logging library dependency. See
`docs/adr/ADR-001-slog-stdlib-backend.md`.
- `logz` owns `ctxRequestIDKey{}` and `ctxExtraFieldsKey{}`. Any package that needs
to attach a request ID to logs imports only `logz`. See
`docs/adr/ADR-002-requestid-context-ownership.md`.
- `Logger` is an exported interface; `New` returns it, not the concrete `*slogLogger`.
`With` returns `Logger`, not `*slogLogger`. See
`docs/adr/ADR-003-exported-logger-interface.md`.
- `Error(msg string, err error, args ...any)` treats `err` as a first-class
parameter, enabling automatic enrichment from duck-typed error interfaces.
## Patterns
**Creating a logger:**
```go
logger := logz.New(logz.Options{
Level: slog.LevelDebug,
JSON: true,
StaticArgs: []any{"service", "api", "env", "production"},
})
```
**Basic logging:**
```go
logger.Info("server started", "port", 8080)
logger.Warn("retrying", "attempt", 3)
logger.Error("request failed", err, "path", "/users")
// If err is *xerrors.Err, error_code and context fields are added automatically
```
**Child loggers:**
```go
reqLogger := logger.With("trace_id", traceID)
reqLogger.Info("handling request")
```
**Request context enrichment:**
```go
// In middleware:
ctx = logz.WithRequestID(ctx, requestID)
ctx = logz.WithField(ctx, "user_id", userID)
ctx = logz.WithFields(ctx, map[string]any{"tenant": tenantID, "region": "eu"})
// In handler (picks up request_id and extra fields automatically):
reqLogger := logger.WithContext(ctx)
reqLogger.Info("processing order")
```
**Retrieving the request ID (e.g. for response headers):**
```go
id := logz.GetRequestID(ctx)
```
**Local Logger interface in a library (ADR-001 pattern):**
```go
// In your library — does NOT import logz
type Logger interface {
Info(msg string, args ...any)
Warn(msg string, args ...any)
Error(msg string, err error, args ...any)
}
// logz.Logger satisfies this interface; pass logz.New(...) from the app layer
```
## What to Avoid
- Do not import `xerrors` from `logz`. The duck-type bridge (`errorWithCode`,
`errorWithContext`) keeps the two packages decoupled.
- Do not return `*slogLogger` from any exported function. The concrete type must
stay unexported so the interface contract is the only public surface.
- Do not write log output to `os.Stderr` or arbitrary `io.Writer`s. Output always
goes to `os.Stdout`; routing is the responsibility of the process supervisor.
- Do not use `slog.Attr` or `slog.Group` in the `Logger` interface. Keep the
variadic `key, value` convention for simplicity.
- Do not call `WithContext(nil)` — the method handles `nil` safely (returns the
same logger), but `WithRequestID` and `WithField` do not accept nil contexts.
- Do not add new methods to the `Logger` interface without a version bump. Any
addition is a breaking change for all callers that replicate the interface locally.
## Testing Notes
- `compliance_test.go` asserts at compile time that `New(Options{})` satisfies
`logz.Logger`, enforcing the full method set at every build.
- `logz_test.go` covers level filtering, JSON vs text mode, `With` chaining,
`WithContext` enrichment, and automatic error enrichment via duck-typed interfaces.
- No external test dependencies — run with plain `go test`.