refactor: framework modules log through diary components

- 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
This commit is contained in:
Jakub Zych
2026-10-21 11:58:22 +02:00
parent 71b99317ad
commit 44291adcca
53 changed files with 185 additions and 130 deletions

View File

@@ -8,11 +8,11 @@ Credential-redacting slog handler that keeps API keys, tokens and passwords out
## 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 calls `sunscreen.InstallDefault(os.Stderr)` as its first statement, so the default logger, the standard `log` package and every plugin that falls back to `slog.Default` log through it from the start.
`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 the application wires into its logger at startup. It is separate because it wraps the standard slog handler and applies to all log output, whichever module writes it.
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:
@@ -30,12 +30,11 @@ Use cases:
## Usage
The generated `main` already installs it:
A program that does not use diary installs it as the default logger:
```go
func run(args []string, out io.Writer) error {
func main() {
sunscreen.InstallDefault(os.Stderr)
cfg, err := compass.Load("config")
// ...
}
```
@@ -55,7 +54,7 @@ Redaction is a safety net, not a licence: log ids and outcomes, never request bo
| 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. |
| `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. |

View File

@@ -87,10 +87,15 @@ func Wrap(next slog.Handler) slog.Handler {
}
// InstallDefault makes a redacting text handler writing to w the process
// default logger (slog.SetDefault), so plugins that fall back to
// slog.Default and the standard log package both log through it. It builds
// a fresh slog.TextHandler instead of wrapping the existing default, whose
// output goes through the log package that SetDefault redirects back here.
// default logger (slog.SetDefault), so code that logs through slog.Default
// and the standard log package both log through it. It builds a fresh
// slog.TextHandler instead of wrapping the existing default, whose output
// goes through the log package that SetDefault redirects back here.
//
// Generated applications no longer call it: they install the diary module,
// which wraps every log channel with Wrap. InstallDefault stays as the
// compatibility entry point for programs that install their own default
// logger.
func InstallDefault(w io.Writer) {
slog.SetDefault(slog.New(Wrap(slog.NewTextHandler(w, nil))))
}