slog-multi

Compose Go's log/slog handlers into fanout, routing, failover, and load-balancing pipelines.

Library
Go
vv1.8.0
637stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
60/100Good
Development Activity56
Maintenance52
Community40
Maturity52
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
80/100Excellent
Architecture85
Code Quality88
Innovation82
Learning Curve65

slog-multi extends Go’s standard library log/slog package with composable handler patterns for building sophisticated logging pipelines. Instead of writing custom slog.Handler wrappers by hand, it provides ready-made primitives — Fanout, Router, FirstMatch, Failover, Pool, and Pipe — that combine and transform any number of underlying handlers.

Each primitive addresses a distinct production logging need: broadcasting a record to every configured destination, conditionally routing records by level, message, or attribute, failing over to a backup sink when the primary is unavailable, distributing load across a pool of handlers, or building middleware chains that transform records before they reach a final handler. Because every construct fully implements the slog.Handler interface, they compose transparently with any other slog-based handler, including samber’s own family of slog-* adapters for Zap, Datadog, Sentry, Slack, and dozens of other sinks.

What You Get

  • Fanout() distributes every log record to multiple slog.Handler instances in parallel, cloning the record so handlers can’t interfere with one another.
  • Router() and its FirstMatch() variant conditionally dispatch records to handlers based on predicates over level, message, or attributes.
  • Failover() tries handlers in priority order and falls through to the next one whenever the current handler errors.
  • Pool() load-balances records across a set of handlers using randomized round robin.
  • Pipe() builds ordered middleware chains that transform, filter, or recover from errors in records before they reach a final handler.

Common Use Cases

  • Sending the same structured log to stdout for local debugging and to a remote sink like Datadog or Loki simultaneously via Fanout.
  • Routing error-level logs to a Slack or paging handler while sending info-level logs to a plain console handler via Router.
  • Keeping logging resilient in production by failing over from a primary log shipper to a local file handler when the network sink is unreachable.
  • Spreading high-volume log traffic across several handler instances to avoid a single bottleneck, using Pool.
  • Wrapping any handler with RecoverHandlerError so a panic or error in one logging sink never crashes the calling application.

Under The Hood

Architecture slog-multi is organized as a flat, single-package library where each composition pattern lives in its own file — Fanout in multi.go, conditional routing in router.go and firstmatch.go, high-availability failover in failover.go, load balancing in pool.go, middleware chaining in pipe.go, and panic/error recovery in recover.go — and every construct fully implements the standard library’s slog.Handler interface (Enabled/Handle/WithAttrs/WithGroup), so any of them can wrap or be wrapped by any other slog.Handler, including deeply nested combinations. State is treated immutably: WithAttrs and WithGroup always return new handler instances rather than mutating in place, matching log/slog’s own contract, and every Handle call clones the record before dispatching to child handlers so one handler’s side effects can never leak into another’s view of the record. A small shared try() helper centralizes panic recovery across every handler type, giving the whole library a uniform failure-handling seam. Because the core abstraction is the stable, external slog.Handler interface rather than a private one, the design is resilient to change even though renaming that interface would ripple through every file.

Tech Stack The library is pure Go, built directly on the standard library’s log/slog package with only a couple of runtime dependencies — samber/lo for functional slice operations and samber/slog-common for attribute/group manipulation helpers shared across samber’s broader family of slog-* handler adapters — plus test-only dependencies for assertions and goroutine-leak detection. There is no web framework, ORM, or database involved; this is a zero-infrastructure extension of the standard library. Continuous integration runs golangci-lint and a security scanner on every push and pull request, and a manual release workflow strips test files and tidies the module before tagging a release.

Code Quality Testing combines the standard library’s testing package with fuzz tests and stress tests that use goroutine-leak detection, a notably rigorous combination for a library of this size, though coverage leans toward the core Fanout and recovery paths rather than every handler type having its own dedicated test file. Error handling is idiomatic — explicit error returns, errors.Join to combine fanout failures, and panics captured through the shared try() helper rather than left to crash the caller. Naming is consistent throughout, and exported types and functions carry extensive, example-laden godoc comments, which is unusually thorough documentation for a project this size. golangci-lint is enforced in CI on every change.

API Design The public API favors small, composable constructors and fluent builders over configuration structs — slog.New(slogmulti.Fanout(h1, h2)) or slogmulti.Router().Add(handler, predicate).Handler() read naturally, and Fanout/Router calls auto-flatten nested compositions so callers never have to pre-flatten handler lists themselves. Predicates are plain functions over context and record, with a comprehensive set of ready-made ones (level, message, and attribute matchers) covering common routing needs without forcing boilerplate matcher code. Because every construct returns a standard slog.Handler, there is no new interface to learn beyond the one the standard library already defines, though some interactions — like how FirstMatch composes with WithGroup — require reading the doc comments closely to use correctly.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search