SKILL.md
$27
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
slog-multi
Handler composition
Fanout, Router, FirstMatch, Failover, Pool, Pipe
slog-sampling
Throughput control
UniformSamplingOption, ThresholdSamplingOption, AbsoluteSamplingOption, CustomSamplingOption
slog-formatter
Attribute transforms
PIIFormatter, ErrorFormatter, FormatByType[T], FormatByKey, FlattenFormatterMiddleware
slog-multi — Handler Composition
Six composition patterns, each for a different routing need:
Pattern
Behavior
Latency impact
Fanout(handlers...)
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
Failover()(handlers...)
Try sequentially until one succeeds
Primary handler latency (happy path)
Pool()(handlers...)
Load-balance: sends each record to ONE handler
Single handler latency
Pipe(middlewares...).Handler(sink)
Middleware chain before sink
Middleware overhead + sink
// 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: LevelIs, LevelIsNot, MessageIs, MessageIsNot, MessageContains, MessageNotContains, AttrValueIs, AttrKindIs.
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.
// 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: MatchByLevel(), MatchByMessage(), MatchByLevelAndMessage() (default), MatchBySource(), MatchByAttribute(groups, key).
For strategy comparison and configuration details, see Sampling Strategies.
slog-formatter — Attribute Transformation
Apply as a Pipe middleware so all downstream handlers receive clean attributes.
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: PIIFormatter, ErrorFormatter, TimeFormatter, UnixTimestampFormatter, IPAddressFormatter, HTTPRequestFormatter, HTTPResponseFormatter. Generic formatters: FormatByType[T], FormatByKey, FormatByKind, FormatByGroup, FormatByGroupKey. Flatten nested attributes with FlattenFormatterMiddleware.
HTTP Middlewares
Consistent pattern across frameworks: router.Use(slogXXX.New(logger)).
Available: slog-gin, slog-echo, slog-fiber, slog-chi, slog-http (net/http).
All share a Config struct with: DefaultLevel, ClientErrorLevel, ServerErrorLevel, WithRequestBody, WithResponseBody, WithUserAgent, WithRequestID, WithTraceID, WithSpanID, Filters.
// 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 Option{}.NewXxxHandler() constructor pattern.
Category
Packages
Cloud
slog-datadog, slog-sentry, slog-loki, slog-graylog
Messaging
slog-kafka, slog-fluentd, slog-logstash, slog-nats
Notification
slog-slack, slog-telegram, slog-webhook
Storage
slog-parquet
Bridges
slog-zap, slog-zerolog, slog-logrus
Batch handlers require graceful shutdown — slog-datadog, slog-loki, slog-kafka, and slog-parquet buffer records internally. Flush on shutdown (e.g., handler.Stop(ctx) for Datadog, lokiClient.Stop() for Loki, writer.Close() 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 Pool() for concurrent dispatch
Missing shutdown flush on batch handlers
Buffered logs lost on shutdown
defer handler.Stop(ctx) (Datadog), defer lokiClient.Stop() (Loki), defer writer.Close() (Kafka)
Router without default/catch-all handler
Unmatched records silently dropped
Add a handler with no predicate as catch-all
AttrFromContext without HTTP middleware
Context has no request attributes to extract
Install slog-gin/echo/fiber/chi middleware first
Using Pipe with no middleware
No-op wrapper adding per-record overhead
Remove Pipe() 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
Pool()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
slog.LogValueron your types instead
- Benchmark your pipeline with
go test -benchbefore 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
AttrFromContext** 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-observabilityskill for slog fundamentals (levels, context, handler setup, migration)
- → See
samber/cc-skills-golang@golang-error-handlingskill for the log-or-return rule
- → See
samber/cc-skills-golang@golang-securityskill for PII handling in logs
- → See
samber/cc-skills-golang@golang-samber-oopsskill for structured error context withsamber/oops
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).