> ## Documentation Index
> Fetch the complete documentation index at: https://docs.westyx.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Go SDK - Logging

> Config.Logger takes a *slog.Logger; structured attributes, per-component scoping, and the guarantee that secrets never appear.

*Changed in v0.11.0: `Config.Logger` is a `*slog.Logger`. The `nexus.Logger` interface and `NoopLogger` are gone.*

```go theme={null}
client, err := nexus.NewClient(ctx, nexus.Config{
    BaseURL: "https://<slug>.westyx.dev",
    APIKey:  os.Getenv("NEXUS_API_KEY"),
    Logger:  slog.Default(),
})
```

A nil `Logger` discards everything.

## Why `*slog.Logger`

`log/slog` is the standard library's structured logger and the ecosystem has settled on
its `Handler` interface, so accepting a `*slog.Logger` reaches every backend without the
SDK depending on any of them. zap, zerolog and logrus all provide a handler, and so does
anything else worth using.

It also makes the output aggregatable. Values arrive as attributes rather than baked
into the message text, so every occurrence of an event shares one message template:
"how often did the stream drop" becomes a countable query rather than a substring search
over lines that are each slightly different.

## What the output looks like

```
level=INFO  msg="client ready" component=client key_type=sk kind=backend ttl=1m0s wif=off stream_idle_timeout=1m30s
level=DEBUG msg="sync complete" component=client configs=12 secrets=3 flags=7 key_type=sk kind=backend
level=DEBUG msg="sync not modified - cache retained" component=client
level=INFO  msg="stream connected" component=stream
level=DEBUG msg="received event - triggering sync" component=stream event=config.updated
level=ERROR msg="stream transport error" component=stream attempt=1 max_attempts=3 error="..."
level=INFO  msg="stream reconnect scheduled" component=stream delay=5m0s
level=INFO  msg="session refreshed" component=wif provider=kubernetes kind=backend expires_in_seconds=3600
```

Every record carries a **`component`** attribute naming the subsystem:

| `component` | Covers                                                          |
| ----------- | --------------------------------------------------------------- |
| `client`    | construction, sync, cache, billing, file-secret materialisation |
| `stream`    | the SSE connection, reconnects, fallbacks, observer faults      |
| `wif`       | token exchange and session refresh                              |

So the three can be routed or filtered independently without parsing message text.

## Levels

| Level   | Used for                                                                                      |
| ------- | --------------------------------------------------------------------------------------------- |
| `Debug` | every sync (success or 304), every SSE event                                                  |
| `Info`  | client ready, stream connected, reconnect scheduled, session refreshed                        |
| `Error` | transport failures, background sync failures, a panicking observer callback, the billing halt |

Nothing above `Error` is ever emitted: the SDK does not decide that an application
should stop.

## Scoping the SDK's output

Pass a logger that already carries attributes of your own and everything the SDK emits
inherits them:

```go theme={null}
Logger: slog.Default().With("subsystem", "nexus", "service", serviceName)
```

To send SDK output somewhere separate from the rest of the application, hand it a
different handler:

```go theme={null}
f, err := os.OpenFile("nexus.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600)
if err != nil {
    return err
}
Logger: slog.New(slog.NewJSONHandler(f, &slog.HandlerOptions{Level: slog.LevelInfo}))
```

To suppress the SDK's debug records while keeping your own, set the level on the handler
you pass in - the SDK has no level setting of its own.

## Secrets never appear

Secret values are never logged, as a message or as an attribute, and neither are secret
key names. The same holds for the API key, the workload OIDC token and the session JWT.

A test drives a sync carrying both a text and a file-type secret through a capturing
handler at debug level and fails if any of them shows up in the output, so this is a
checked property rather than a convention.

## Observer faults

A [`StreamObserver`](/sdks/go/stream-observer) callback that panics is recovered, and the fault is
reported here at `Error` with the hook name and a stack trace:

```
level=ERROR msg="observer callback panicked and was skipped" component=stream hook=OnEvent panic="..." stack="..."
```

The stream stays up.

## Next

* [Configuration](/sdks/go/configuration)
* [Stream observer](/sdks/go/stream-observer)
* [Error handling](/sdks/go/error-handling)
