# Logging

Log structured events through one interface and swap slog, zerolog, pretty, or noop by config.

Every service logs through one interface, `log.Logger`. Swap the adapter and every line changes shape with no call-site changes: `noop` for tests, `slog` for the stdlib, `zerolog` for high-throughput JSON, `pretty` for local development. Resolve one logger per process and inject it.

## Quick start

```sh
LOG_ADAPTER=slog
```

```sh
LOG_ADAPTER=zerolog
```

The only option is `min_level` (minimum severity). `Options.Validate()` accepts the zero value, and the adapter choice is validated at resolve time. The container wires nothing, so register the adapter first:

```go title="main.go"
import (
    "github.com/zenta-dev/zever/adapters/log/slog"
    "github.com/zenta-dev/zever/config"
    "github.com/zenta-dev/zever/container"
)

slog.Register()
c := container.New(config.Default())
logger, err := c.Log()
if err != nil {
    return err
}
handler := middleware.RequestLogger(logger)(mux)
```

`Logger` has no `Close`; logging probes as a no-op in `c.Close(ctx)`. Full keys: [log config reference](https://zenta-dev.github.io/zever/reference/config/#log).

## Write typed fields

`Event` has one typed method per kind, so `slog` and `zerolog` output stays queryable instead of a flat string.

```go title="handler.go"
logger.Info().Str("user_id", userID).Dur("duration", elapsed).Msg("request handled")
logger.Error().Err(err).Msg("failed to process order")

if logger.Enabled(log.LevelDebug) {
    logger.Debug().Any("payload", expensiveToCompute()).Send()
}
```

```text
level=INFO msg="request handled" user_id=u_123 duration=12ms
```

Prefer `Str`/`Int`/`Dur` over `Any` for hot fields, because `Any` uses reflection. Reserve `Any` for dynamic payloads, and never pass request bodies, headers, or config maps through it. `RequestLogger` emits one line per request (method, path, status, `duration_ms` (fractional milliseconds), plus `request_id` when present); `Recover` logs panics with value and stack, then responds 500. Emit one line per failure, with no duplicates.

The API: `Logger` exposes `Debug()`, `Info()`, `Warn()`, `Error()`, `Fatal()`, `With()`, `WithContext(ctx)`, `Enabled(level)`, `Sync()`, and `Name()`. `Event` exposes `Str`, `Int`, `Int64`, `Float64`, `Bool`, `Dur`, `Time`, `Err`, `AnErr(key, err)`, `Any`, `Msg`, `Msgf(format, args...)`, and `Send`. Helpers: `log.ContextWithLogger(ctx, l)`, `log.FromContext(ctx, fallback)`, `log.Open(adapter, opts)`, and `middleware.RequestLogger(logger)`.

## Carry a logger through context

`log.ContextWithLogger(ctx, logger)` carries only the logger value, no I/O state. `log.FromContext(ctx, fallback)` returns it, or the fallback for contexts that carry none, so `request_id` and other attached fields survive the call chain.

## Keep secrets out of logs

Never pass raw config or headers into `Any`, which may print DSNs and keys. Use `cfg.RedactedServices()` and fixed field names. The same rule covers error text: name services and fields, never values. See [Configuration](https://zenta-dev.github.io/zever/guides/operate/configuration/).

## Production checklist

- One process, one adapter: `slog` for stdlib-only builds, `zerolog` for high-throughput JSON, `pretty` for local shells, `noop` for tests.
- Buffered providers flush through their own `Shutdown(ctx)`, which `c.Close(ctx)` probes. Do not exit before `Close` returns or trailing lines drop.
- Shutdown never blocks past 5s per service (or the parent deadline). Keep log I/O fast and never format unbounded payloads.

## Troubleshooting

<details>
<summary>No log output in tests</summary>

The resolved adapter is `noop`, correct for hermetic tests. Assert behavior, not lines, or resolve `slog` explicitly.
</details>

<details>
<summary>`request_id` is missing from lines</summary>

The context logger was never attached. Wrap the context with `log.ContextWithLogger` in middleware before handlers run.
</details>

<details>
<summary>Secrets in log files</summary>

A call passes raw config or headers into `Any`. Use `cfg.RedactedServices()` and fixed field names.
</details>

<details>
<summary>Expensive debug work on hot paths</summary>

Fields compute even when the level is off. Gate with `logger.Enabled(log.LevelDebug)`.
</details>

<details>
<summary>Trailing lines lost at exit</summary>

The process exits before `c.Close(ctx)` finishes. Defer `Close` with a timeout parent and check the joined error.
</details>

## Related

- [Errors](https://zenta-dev.github.io/zever/guides/http/errors/): Redaction in error text.
- [Middleware](https://zenta-dev.github.io/zever/guides/http/middleware/): RequestLogger and Recover.
- [Configuration](https://zenta-dev.github.io/zever/guides/operate/configuration/): Precedence and redaction.
