- Each module logs through diary.For(<package>): cabana, lagoon, attach, cooler, postcard, beachcomber, lighthouse, centrifugo, flare, phrasebook, wristband, bouncer - App-published *slog.Logger overrides stay first; messages and attributes unchanged - sunscreen: InstallDefault documented as the compatibility entry point; diary wraps every channel - READMEs list diary as a dependency; logging docs: plugins use diary.For
75 lines
4.3 KiB
Markdown
75 lines
4.3 KiB
Markdown
# sunscreen
|
|
|
|
Credential-redacting slog handler that keeps API keys, tokens and passwords out of application logs.
|
|
|
|
`import "git.golem15.com/golem15/summercms/modules/sunscreen"`
|
|
|
|
**Name:** Protects you from exposure; it keeps secrets out of the logs.
|
|
|
|
## Overview
|
|
|
|
`sunscreen` wraps any `log/slog` handler and rewrites every record before it is written: values of sensitive attribute keys become `[REDACTED]`, and credential shapes inside messages and string values are scrubbed. It is the SummerCMS counterpart of a Monolog tap that a WinterCMS application wires into its log channels to scrub contexts and messages. Every application `main` that `summer build` generates installs [diary](../diary/README.md), which wraps each of its log channels (console, stderr, daily files, null) with `sunscreen.Wrap`, so component loggers, the default logger and the standard `log` package all log through it from the start. `sunscreen.InstallDefault` stays as the compatibility entry point for programs that install their own default logger; there is no other install function.
|
|
|
|
## Role in the framework
|
|
|
|
A dependency-free leaf that diary wraps around every log channel. It is separate because it wraps the standard slog handler and applies to all log output, whichever module writes it.
|
|
|
|
Use cases:
|
|
|
|
- Keep API keys, tokens and passwords out of logs, including logs from third-party code.
|
|
- Log request and configuration details while debugging without leaking secrets.
|
|
- Meet a "no credentials in logs" audit requirement in one place.
|
|
|
|
## Features
|
|
|
|
- Sensitive keys (`sunscreen.RedactedKeys`): `api_key`, `apikey`, `authorization`, `bearer`, `password`, `secret`, `token`, `webhook_secret`, `admin_password`, `openai_api_key`, `anthropic_api_key` and `perplexity_api_key`, compared case-insensitively at any group depth. The value of such an attribute becomes `sunscreen.Redacted`, whatever its kind.
|
|
- Scrub patterns (`sunscreen.Scrub`): `Bearer <token>` becomes `Bearer [REDACTED]`, `sk-` followed by 20 or more key characters (dashed keys included) becomes `sk-[REDACTED]`, and `x-api-key: <value>` becomes `x-api-key: [REDACTED]`. The Bearer and x-api-key matches are case-insensitive.
|
|
- Applied to the record message, string values, error values, byte slices and the formatted text of any other value; maps with string keys, such as `http.Header`, are redacted key by key.
|
|
- `slog.LogValuer` values are resolved before redaction, groups are walked recursively, and attributes added with `WithAttrs` are redacted before they reach the wrapped handler.
|
|
- `sunscreen.InstallDefault` builds a fresh text handler instead of wrapping the existing default, whose output goes through the `log` package that `slog.SetDefault` redirects back into the new handler.
|
|
|
|
## Usage
|
|
|
|
A program that does not use diary installs it as the default logger:
|
|
|
|
```go
|
|
func main() {
|
|
sunscreen.InstallDefault(os.Stderr)
|
|
// ...
|
|
}
|
|
```
|
|
|
|
To redact another handler, for example a JSON handler in a custom binary, wrap it:
|
|
|
|
```go
|
|
logger := slog.New(sunscreen.Wrap(slog.NewJSONHandler(os.Stderr, nil)))
|
|
logger.Error("vendor call failed", "api_key", key, "err", err)
|
|
// {"level":"ERROR","msg":"vendor call failed","api_key":"[REDACTED]","err":"..."}
|
|
```
|
|
|
|
Redaction is a safety net, not a licence: log ids and outcomes, never request bodies, tokens or arguments that carry them.
|
|
|
|
## API reference
|
|
|
|
| Identifier | Description |
|
|
|------------|-------------|
|
|
| `sunscreen.Wrap` | Returns a `slog.Handler` that redacts every record and every `WithAttrs` attribute before the wrapped handler sees it. Wrapping a sunscreen handler again returns it unchanged. |
|
|
| `sunscreen.InstallDefault` | Makes a redacting text handler writing to the given writer the process default logger; the compatibility entry point for programs that do not install diary. |
|
|
| `sunscreen.Scrub` | Replaces the Bearer, `sk-` and `x-api-key:` credential shapes in a string. |
|
|
| `sunscreen.Redacted` | The replacement text, `[REDACTED]`. |
|
|
| `sunscreen.RedactedKeys` | Returns a copy of the attribute keys whose values are always redacted. |
|
|
|
|
## Dependencies
|
|
|
|
- SummerCMS modules: none.
|
|
- Third-party: none.
|
|
- Standard library: `context`, `fmt`, `io`, `log/slog`, `reflect`, `regexp`, `slices`, `strings`.
|
|
|
|
## Testing
|
|
|
|
```sh
|
|
go test ./modules/sunscreen/...
|
|
```
|
|
|
|
The tests log through the handler into a buffer and need no external services.
|