Persona: You are a Go logging architect. You design log pipelines where every record flows through the right handlers — sampling drops noise early, formatters strip PII before records leave the process, and routers send errors to Sentry while info goes to Loki.
samber/slog-**** — Structured Logging Pipeline for Go
20+ composable
packages for Go 1.21+. Three core pipeline libraries plus HTTP middlewares and backend sinks that all implement the standard
interface.
Official resources:
This skill is not exhaustive. Please refer to library documentation and code examples for more informations. Context7 can help as a discoverability platform.
The Pipeline Model
Every samber/slog pipeline follows a canonical ordering. Records flow left to right — place sampling first to drop early and avoid wasting CPU on records that never reach a sink.
record → [Sampling] → [Pipe: trace/PII] → [Router] → [Sinks]
Order matters: sampling before formatting saves CPU. Formatting before routing ensures all sinks receive clean attributes. Reversing this wastes work on records that get dropped.
Core Libraries
| Library | Purpose | Key constructors |
|---|
| Handler composition | , , , , , |
| Throughput control | , , , |
| Attribute transforms | , , , , FlattenFormatterMiddleware
|
slog-multi — Handler Composition
Six composition patterns, each for a different routing need:
| Pattern | Behavior | Latency impact |
|---|
| Broadcast to all handlers sequentially | Sum of all handler latencies |
Router().Add(h, predicate).Handler()
| Route to ALL matching handlers | Sum of matching handlers |
Router().Add(...).FirstMatch().Handler()
| Route to FIRST match only | Single handler latency |
| Try sequentially until one succeeds | Primary handler latency (happy path) |
| Concurrent broadcast to all handlers | Max of all handler latencies |
Pipe(middlewares...).Handler(sink)
| Middleware chain before sink | Middleware overhead + sink |
go
// Route errors to Sentry, all logs to stdout
logger := slog.New(
slogmulti.Router().
Add(sentryHandler, slogmulti.LevelIs(slog.LevelError)).
Add(slog.NewJSONHandler(os.Stdout, nil)).
Handler(),
)
Built-in predicates:
,
,
,
,
,
,
,
.
For full code examples of every pattern, see Pipeline Patterns.
slog-sampling — Throughput Control
| Strategy | Behavior | Best for |
|---|
| Uniform | Drop fixed % of all records | Dev/staging noise reduction |
| Threshold | Log first N per interval, then sample at rate R | Production — preserves initial visibility |
| Absolute | Cap at N records per interval globally | Hard cost control |
| Custom | User function returns sample rate per record | Level-aware or time-aware rules |
Sampling MUST be the outermost handler in the pipeline — placing it after formatting wastes CPU on records that get dropped.
go
// Threshold: log first 10 per 5s, then 10% — errors always pass through via Router
logger := slog.New(
slogmulti.
Pipe(slogsampling.ThresholdSamplingOption{
Tick: 5 * time.Second, Threshold: 10, Rate: 0.1,
}.NewMiddleware()).
Handler(innerHandler),
)
Matchers group similar records for deduplication:
,
,
(default),
,
MatchByAttribute(groups, key)
.
For strategy comparison and configuration details, see Sampling Strategies.
slog-formatter — Attribute Transformation
Apply as a
middleware so all downstream handlers receive clean attributes.
go
logger := slog.New(
slogmulti.Pipe(slogformatter.NewFormatterMiddleware(
slogformatter.PIIFormatter("user"), // mask PII fields
slogformatter.ErrorFormatter("error"), // structured error info
slogformatter.IPAddressFormatter("client"), // mask IP addresses
)).Handler(slog.NewJSONHandler(os.Stdout, nil)),
)
Key formatters:
,
,
,
,
,
,
. Generic formatters:
,
,
,
,
. Flatten nested attributes with
FlattenFormatterMiddleware
.
HTTP Middlewares
Consistent pattern across frameworks:
router.Use(slogXXX.New(logger))
.
Available:
,
,
,
,
(net/http).
All share a
struct with:
,
,
,
,
,
,
,
,
,
.
go
// Gin with filters — skip health checks
router.Use(sloggin.NewWithConfig(logger, sloggin.Config{
DefaultLevel: slog.LevelInfo,
ClientErrorLevel: slog.LevelWarn,
ServerErrorLevel: slog.LevelError,
WithRequestBody: true,
Filters: []sloggin.Filter{
sloggin.IgnorePath("/health", "/metrics"),
},
}))
For framework-specific setup, see HTTP Middlewares.
Backend Sinks
All follow the
constructor pattern.
| Category | Packages |
|---|
| Cloud | , , , |
| Messaging | , , , |
| Notification | , , |
| Storage | |
| Bridges | , , |
Batch handlers require graceful shutdown —
,
,
, and
buffer records internally. Flush on shutdown (e.g.,
for Datadog,
for Loki,
for Kafka) or buffered logs are lost.
For configuration examples and shutdown patterns, see Backend Handlers.
Common Mistakes
| Mistake | Why it fails | Fix |
|---|
| Sampling after formatting | Wastes CPU formatting records that get dropped | Place sampling as outermost handler |
| Fanout to many synchronous handlers | Blocks caller — latency is sum of all handlers | Use for concurrent dispatch |
| Missing shutdown flush on batch handlers | Buffered logs lost on shutdown | (Datadog), (Loki), (Kafka) |
| Router without default/catch-all handler | Unmatched records silently dropped | Add a handler with no predicate as catch-all |
| without HTTP middleware | Context has no request attributes to extract | Install /// middleware first |
| Using with no middleware | No-op wrapper adding per-record overhead | Remove if no middleware needed |
Performance Warnings
- Fanout latency = sum of all handler latencies (sequential). With 5 handlers at 10ms each, every log call costs 50ms. Use to reduce to max(latencies)
- Pipe middleware adds per-record function call overhead — keep chains short (2-4 middlewares)
- slog-formatter processes attributes sequentially — many formatters compound. For hot-path attribute formatting, prefer implementing on your types instead
- Benchmark your pipeline with before production deployment
Diagnose: measure per-record allocation and latency of your pipeline and identify which handler in the chain allocates most.
Best Practices
- Sample first, format second, route last — this canonical ordering minimizes wasted work and ensures all sinks see clean data
- Use Pipe for cross-cutting concerns — trace ID injection and PII scrubbing belong in middleware, not per-handler logic
- Test pipelines with
slogmulti.NewHandleInlineHandler
— assert on records reaching each stage without real sinks
- Use to propagate request-scoped attributes from HTTP middleware to all handlers
- Prefer Router over Fanout when handlers need different record subsets — Router evaluates predicates and skips non-matching handlers
Cross-References
- → See
samber/cc-skills-golang@golang-observability
skill for slog fundamentals (levels, context, handler setup, migration)
- → See
samber/cc-skills-golang@golang-error-handling
skill for the log-or-return rule
- → See
samber/cc-skills-golang@golang-security
skill for PII handling in logs
- → See
samber/cc-skills-golang@golang-samber-oops
skill for structured error context with
If you encounter a bug or unexpected behavior in any samber/slog-* package, open an issue at the relevant repository (e.g.,
slog-multi/issues,
slog-sampling/issues).