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

# Logging

Copper provides the `clogger` package for structured, leveled logging. You may inject `clogger.Logger` anywhere you need it, and every scaffolded router and service already has one.

### Writing Logs

There are four levels. `Debug` and `Info` take just a message, while `Warn` and `Error` also take an error, which may be `nil`:

```go theme={null}
logger.Info("Rocket launched")
logger.Warn("Telemetry delayed", nil)
logger.Error("Failed to launch rocket", err)
```

Structured context comes from `WithTags`:

```go theme={null}
logger.WithTags(map[string]any{
	"rocketId": id,
	"attempt":  attempt,
}).Info("Launch scheduled")
```

When you pass an error, tags from its entire [cerrors](/the-basics/error-handling) chain are merged into the log automatically, so if you wrap errors well, your logs arrive pre-annotated:

```
2026/08/24 10:15:04 [ERROR] Failed to launch rocket where rocketId=falcon-9 because
> failed to get launch where launchId=lc-42 because
> sql: no rows in result set
```

### Configuration

The logger is configured under the `[clogger]` section:

```toml theme={null}
[clogger]
format = "json"
level_filter = ["info", "warn", "error"]
```

| Key             | Default    | Description                                    |
| --------------- | ---------- | ---------------------------------------------- |
| `format`        | `plain`    | `plain` for development, `json` for production |
| `out`           | stdout     | File path for Debug/Info logs                  |
| `err`           | stderr     | File path for Warn/Error logs                  |
| `level_filter`  | all levels | Which levels to log                            |
| `redact_fields` | none       | Tag fields to redact (JSON format only)        |

In JSON format, each log is a single object with `ts`, `level`, `msg`, `error`, and `tags`, ready for a log aggregator.

<Warning>
  `level_filter` is an allow-list, not a threshold. `level_filter = ["error"]` logs *only* errors. If you would like warnings as well, list both.
</Warning>

### Redacting Sensitive Fields

In JSON format, `redact_fields` replaces matching tag values with `"redacted"` anywhere in the tag tree, including nested objects. Field names match loosely, so `redact_fields = ["email"]` also catches `user_email` and `emailAddress`:

```toml theme={null}
[clogger]
format = "json"
redact_fields = ["password", "email"]
```

<Warning>
  Redaction only works with `format = "json"`. In plain format, setting `redact_fields` replaces every log message with a placeholder rather than risk leaking a field.
</Warning>

### Hooks

If you would like to ship logs to an external service, such as an error tracker or a cloud logging platform, implement `clogger.Hook`:

```go theme={null}
type Hook interface {
	OnLog(level Level, msg string, tags map[string]any, err error)
}
```

Hooks run for every log that passes the level filter. You may provide them as a `[]clogger.Hook` in your app's wire module:

```go pkg/app/logger.go theme={null}
func ProvideLoggerHooks(tracker *errtracker.Hook) []clogger.Hook {
	return []clogger.Hook{tracker}
}
```
