samber/slog-multi
Snapshot of samber/slog-multi: 638β , Go, Backend. π¨ Design workflows of slog handlers: pipeline, middleware, fanout, routing, failover, load balancing...
Snapshot summary built from the project's own GitHub metadata β there's no written TopGit review yet. The page will update automatically when a full review is published.
TopGit writes full reviews for the most-starred, most-requested repositories. This page is a snapshot until then β see the READ ME tab for the original README in full.
Snapshot
Read our review methodology β
Top contributors
Show top contributors
slog-multi: Advanced Handler Composition for Go's Structured Logging (pipelining, fanout, routing, failover...)
slog-multi provides advanced composition patterns for Go's structured logging (slog). It enables you to build sophisticated logging workflows by combining multiple handlers with different strategies for distribution, routing, transformation, and error handling.
π― Features
- π Fanout: Distribute logs to multiple handlers in parallel
- π£οΈ Router: Conditionally route logs based on custom criteria
- π― First Match: Route logs to the first matching handler only
- π Failover: High-availability logging with automatic fallback
- βοΈ Load Balancing: Distribute load across multiple handlers
- π Pipeline: Transform and filter logs with middleware chains
- π‘οΈ Error Recovery: Graceful handling of logging failures
Middlewares:
- β‘ Inline Handlers: Quick implementation of custom handlers
- π§ Inline Middleware: Rapid development of transformation logic
Sponsored by:
Simple to use, built on open standards, and designed for full cost control
See also:
- slog-multi:
slog.Handlerchaining, fanout, routing, failover, load balancing... - slog-formatter:
slogattribute formatting - slog-sampling:
slogsampling policy - slog-mock:
slog.Handlerfor test purposes
HTTP middlewares:
- slog-gin: Gin middleware for
sloglogger - slog-echo: Echo middleware for
sloglogger - slog-fiber: Fiber middleware for
sloglogger - slog-chi: Chi middleware for
sloglogger - slog-http:
net/httpmiddleware forsloglogger
Loggers:
- slog-zap: A
sloghandler forZap - slog-zerolog: A
sloghandler forZerolog - slog-logrus: A
sloghandler forLogrus
Log sinks:
- slog-datadog: A
sloghandler forDatadog - slog-betterstack: A
sloghandler forBetterstack - slog-rollbar: A
sloghandler forRollbar - slog-loki: A
sloghandler forLoki - slog-sentry: A
sloghandler forSentry - slog-syslog: A
sloghandler forSyslog - slog-logstash: A
sloghandler forLogstash - slog-fluentd: A
sloghandler forFluentd - slog-graylog: A
sloghandler forGraylog - slog-quickwit: A
sloghandler forQuickwit - slog-slack: A
sloghandler forSlack - slog-telegram: A
sloghandler forTelegram - slog-mattermost: A
sloghandler forMattermost - slog-microsoft-teams: A
sloghandler forMicrosoft Teams - slog-webhook: A
sloghandler forWebhook - slog-kafka: A
sloghandler forKafka - slog-nats: A
sloghandler forNATS - slog-parquet: A
sloghandler forParquet+Object Storage - slog-channel: A
sloghandler for Go channels
π Installation
go get github.com/samber/slog-multi
Compatibility: go >= 1.21
No breaking changes will be made to exported APIs before v2.0.0.
[!WARNING] Use this library carefully, log processing can be very costly (!)
Excessive logging βwith multiple processing steps and destinationsβ can introduce significant overhead, which is generally undesirable in performance-critical paths. Logging is always expensive, and sometimes, metrics or a sampling strategy are cheaper. The library itself does not generate extra load.
π‘ Usage
GoDoc: https://pkg.go.dev/github.com/samber/slog-multi
Broadcast: slogmulti.Fanout()
Distribute logs to multiple slog.Handler in parallel for maximum throughput and redundancy.
import (
"net"
slogmulti "github.com/samber/slog-multi"
"log/slog"
"os"
"time"
)
func main() {
logstash, _ := net.Dial("tcp", "logstash.acme:4242") // use github.com/netbrain/goautosocket for auto-reconnect
datadogHandler := slogdatadog.NewDatadogHandler(slogdatadog.Option{
APIKey: "your-api-key",
Service: "my-service",
})
stderr := os.Stderr
logger := slog.New(
slogmulti.Fanout(
slog.NewJSONHandler(logstash, &slog.HandlerOptions{}), // pass to first handler: logstash over tcp
slog.NewTextHandler(stderr, &slog.HandlerOptions{}), // then to second handler: stderr
datadogHandler,
// ...
),
)
logger.
With(
slog.Group("user",
slog.String("id", "user-123"),
slog.Time("created_at", time.Now()),
),
).
With("environment", "dev").
With("error", fmt.Errorf("an error")).
Error("A message")
}
Stderr output:
time=2023-04-10T14:00:0.000000+00:00 level=ERROR msg="A message" user.id=user-123 user.created_at=2023-04-10T14:00:0.000000+00:00 environment=dev error="an error"
Netcat output:
{
"time":"2023-04-10T14:00:0.000000+00:00",
"level":"ERROR",
"msg":"A message",
"user":{
"id":"user-123",
"created_at":"2023-04-10T14:00:0.000000+00:00"
},
"environment":"dev",
"error":"an error"
}
Routing: slogmulti.Router()
Distribute logs to all matching slog.Handler based on custom criteria like log level, attributes, or business logic.
import (
"context"
slogmulti "github.com/samber/slog-multi"
slogslack "github.com/samber/slog-slack"
"log/slog"
"os"
)
func main() {
slackChannelUS := slogslack.Option{Level: slog.LevelError, WebhookURL: "xxx", Channel: "supervision-us"}.NewSlackHandler()
slackChannelEU := slogslack.Option{Level: slog.LevelError, WebhookURL: "xxx", Channel: "supervision-eu"}.NewSlackHandler()
slackChannelAPAC := slogslack.Option{Level: slog.LevelError, WebhookURL: "xxx", Channel: "supervision-apac"}.NewSlackHandler()
consoleHandler := slog.NewTextHandler(os.Stderr, nil)
logger := slog.New(
slogmulti.Router().
Add(slackChannelUS, recordMatchRegion("us")).
Add(slackChannelEU, recordMatchRegion("eu")).
Add(slackChannelAPAC, recordMatchRegion("apac")).
Add(consoleHandler, slogmulti.LevelIs(slog.LevelInfo, slog.LevelDebug)).
Handler(),
)
logger.
With("region", "us").
With("pool", "us-east-1").
Error("Server desynchronized")
}
func recordMatchRegion(region string) func(ctx context.Context, r slog.Record) bool {
return func(ctx context.Context, r slog.Record) bool {
ok := false
r.Attrs(func(attr slog.Attr) bool {
if attr.Key == "region" && attr.Value.Kind() == slog.KindString && attr.Value.String() == region {
ok = true
return false
}
return true
})
return ok
}
}
Use Cases:
- Environment-specific logging (dev vs prod)
- Level-based routing (errors to Slack, info to console)
- Business logic routing (user actions vs system events)
First Match Routing: Router().FirstMatch()
Route logs to the first matching handler only, unlike regular routing which sends to all matching handlers. Perfect for priority-based routing where you want exactly one handler to receive each log.
import (
slogmulti "github.com/samber/slog-multi"
slogslack "github.com/samber/slog-slack"
"log/slog"
)
func main() {
queryChannel := slogslack.Option{Level: slog.LevelDebug, WebhookURL: "xxx", Channel: "db-queries"}.NewSlackHandler()
requestChannel := slogslack.Option{Level: slog.LevelError, WebhookURL: "xxx", Channel: "service-requests"}.NewSlackHandler()
influxdbChannel := slogslack.Option{Level: slog.LevelInfo, WebhookURL: "xxx", Channel: "influxdb-metrics"}.NewSlackHandler()
fallbackChannel := slogslack.Option{Level: slog.LevelError, WebhookURL: "xxx", Channel: "logs"}.NewSlackHandler()
logger := slog.New(
slogmulti.Router().
Add(queryChannel, slogmulti.AttrKindIs("query", slog.KindString, "args", slog.KindAny)).
Add(requestChannel, slogmulti.AttrKindIs("method", slog.KindString, "body", slog.KindAny)).
Add(influxdbChannel, slogmulti.AttrValueIs("scope", "influx")).
Add(fallbackChannel). // Catch-all for everything else
FirstMatch(). // β Enable first-match routing
Handler(),
)
// Goes to queryChannel only (stops at first match)
logger.Debug("Executing SQL query", "query", "SELECT * FROM users WHERE id = ?", "args", []int{1})
// Goes to requestChannel only (stops at first match)
logger.Error("Incoming request failed", "method", "POST", "body", "{'name':'test'}")
// Goes to fallbackChannel (no other handlers matched)
logger.Error("An unexpected error occurred")
}
Built-in Predicates
Level predicates:
LevelIs(levels ...slog.Level)- Match specific log levelsLevelIsNot(levels ...slog.Level)- Exclude specific log levels
Message predicates:
MessageIs(msg string)- Exact message matchMessageIsNot(msg string)- Message doesn't matchMessageContains(part string)- Message contains substringMessageNotContains(part string)- Message doesn't contain substring
Attribute predicates:
AttrValueIs(key, value, ...)- Check attributes have exact valuesAttrKindIs(key, kind, ...)- Check attributes have specific types
Failover: slogmulti.Failover()
Ensure logging reliability by trying multiple handlers in order until one succeeds. Perfect for high-availability scenarios.
import (
"net"
slogmulti "github.com/samber/slog-multi"
"log/slog"
"os"
"time"
)
func main() {
// Create connections to multiple log servers
// ncat -l 1000 -k
// ncat -l 1001 -k
// ncat -l 1002 -k
// List AZs - use github.com/netbrain/goautosocket for auto-reconnect
logstash1, _ := net.Dial("tcp", "logstash.eu-west-3a.internal:1000")
logstash2, _ := net.Dial("tcp", "logstash.eu-west-3b.internal:1000")
logstash3, _ := net.Dial("tcp", "logstash.eu-west-3c.internal:1000")
logger := slog.New(
slogmulti.Failover()(
slog.HandlerOptions{}.NewJSONHandler(logstash1, nil), // Primary
slog.HandlerOptions{}.NewJSONHandler(logstash2, nil), // Secondary
slog.HandlerOptions{}.NewJSONHandler(logstash3, nil), // Tertiary
),
)
logger.
With(
slog.Group("user",
slog.String("id", "user-123"),
slog.Time("created_at", time.Now()),
),
).
With("environment", "dev").
With("error", fmt.Errorf("an error")).
Error("A message")
}
Use Cases:
- High-availability logging infrastructure
- Disaster recovery scenarios
- Multi-region deployments
Load balancing: slogmulti.Pool()
Distribute logging load across multiple handlers using round-robin with randomization to increase throughput and provide redundancy.
import (
"net"
slogmulti "github.com/samber/slog-multi"
"log/slog"
"os"
"time"
)
func main() {
// Create multiple log servers
// ncat -l 1000 -k
// ncat -l 1001 -k
// ncat -l 1002 -k
// List AZs - use github.com/netbrain/goautosocket for auto-reconnect
logstash1, _ := net.Dial("tcp", "logstash.eu-west-3a.internal:1000")
logstash2, _ := net.Dial("tcp", "logstash.eu-west-3b.internal:1000")
logstash3, _ := net.Dial("tcp", "logstash.eu-west-3c.internal:1000")
logger := slog.New(
slogmulti.Pool()(
// A random handler will be picked for each log
slog.HandlerOptions{}.NewJSONHandler(logstash1, nil),
slog.HandlerOptions{}.NewJSONHandler(logstash2, nil),
slog.HandlerOptions{}.NewJSONHandler(logstash3, nil),
),
)
// High-volume logging
for i := 0; i < 1000; i++ {
logger.
With(
slog.Group("user",
slog.String("id", "user-123"),
slog.Time("created_at", time.Now()),
),
).
With("environment", "dev").
With("error", fmt.Errorf("an error")).
Error("A message")
}
}
Use Cases:
- High-throughput logging scenarios
- Distributed logging infrastructure
- Performance optimization
Recover errors: slogmulti.RecoverHandlerError()
Gracefully handle logging failures without crashing the application. Catches both panics and errors from handlers.
import (
"context"
slogformatter "github.com/samber/slog-formatter"
slogmulti "github.com/samber/slog-multi"
"log/slog"
"os"
)
recovery := slogmulti.RecoverHandlerError(
func(ctx context.Context, record slog.Record, err error) {
// will be called only if subsequent handlers fail or return an error
log.Println(err.Error())
},
)
sink := NewSinkHandler(...)
logger := slog.New(
slogmulti.
Pipe(recovery).
Handler(sink),
)
err := fmt.Errorf("an error")
logger.Error("a message",
slog.Any("very_private_data", "abcd"),
slog.Any("user", user),
slog.Any("err", err))
// outputs:
// time=2023-04-10T14:00:0.000000+00:00 level=ERROR msg="a message" error.message="an error" error.type="*errors.errorString" user="John doe" very_private_data="********"
Pipelining: slogmulti.Pipe()
Transform and filter logs using middleware chains. Perfect for data privacy, formatting, and cross-cutting concerns.
import (
"context"
slogmulti "github.com/samber/slog-multi"
"log/slog"
"os"
"time"
)
func main() {
// First middleware: format Go `error` type into an structured object {error: "*myCustomErrorType", message: "could not reach https://a.b/c"}
errorFormattingMiddleware := slogmulti.NewHandleInlineMiddleware(func(ctx context.Context, record slog.Record, next func(context.Context, slog.Record) error) error {
record.Attrs(func(attr slog.Attr) bool {
if attr.Key == "error" && attr.Value.Kind() == slog.KindAny {
if err, ok := attr.Value.Any().(error); ok {
record.AddAttrs(
slog.String("error_type", "error"),
slog.String("error_message", err.Error()),
)
}
}
return true
})
return next(ctx, record)
})
// Second middleware: remove PII
gdprMiddleware := slogmulti.NewHandleInlineMiddleware(func(ctx context.Context, record slog.Record, next func(context.Context, slog.Record) error) error {
record.Attrs(func(attr slog.Attr) bool {
if attr.Key == "email" || attr.Key == "phone" || attr.Key == "created_at" {
record.AddAttrs(slog.String(attr.Key, "*********"))
}
return true
})
return next(ctx, record)
})
// Final handler
sink := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{})
logger := slog.New(
slogmulti.
Pipe(errorFormattingMiddleware).
Pipe(gdprMiddleware).
// ...
Handler(sink),
)
logger.
With(
slog.Group("user",
slog.String("id", "user-123"),
slog.String("email", "user-123"),
slog.Time("created_at", time.Now()),
),
).
With("environment", "dev").
Error("A message",
slog.String("foo", "bar"),
slog.Any("error", fmt.Errorf("an error")),
)
}
Stderr output:
{
"time":"2023-04-10T14:00:0.000000+00:00",
"level":"ERROR",
"msg":"A message",
"user":{
"email":"*******",
"phone":"*******",
"created_at":"*******"
},
"environment":"dev",
"foo":"bar",
"error":{
"type":"*myCustomErrorType",
"message":"an error"
}
}
Use Cases:
- Data privacy and GDPR compliance
- Error formatting and standardization
- Log enrichment and transformation
- Performance monitoring and metrics
π§ Advanced Patterns
Custom middleware
Middleware must match the following prototype:
type Middleware func(slog.Handler) slog.Handler
The example above uses:
- a custom middleware, see here
- an inline middleware, see here
Note:
WithAttrsandWithGroupmethods of custom middleware must return a new instance, notthis.
Inline handler
Inline handlers provide shortcuts to implement slog.Handler without creating full struct implementations.
mdw := slogmulti.NewHandleInlineHandler(
// simulate "Handle()" method
func(ctx context.Context, groups []string, attrs []slog.Attr, record slog.Record) error {
// Custom logic here
// [...]
return nil
},
)
mdw := slogmulti.NewInlineHandler(
// simulate "Enabled()" method
func(ctx context.Context, groups []string, attrs []slog.Attr, level slog.Level) bool {
// Custom logic here
// [...]
return true
},
// simulate "Handle()" method
func(ctx context.Context, groups []string, attrs []slog.Attr, record slog.Record) error {
// Custom logic here
// [...]
return nil
},
)
Inline middleware
Inline middleware provides shortcuts to implement middleware functions that hook specific methods.
Hook Enabled() Method
middleware := slogmulti.NewEnabledInlineMiddleware(func(ctx context.Context, level slog.Level, next func(context.Context, slog.Level) bool) bool{
// Custom logic before calling next
if level == slog.LevelDebug {
return false // Skip debug logs
}
return next(ctx, level)
})
Hook Handle() Method
middleware := slogmulti.NewHandleInlineMiddleware(func(ctx context.Context, record slog.Record, next func(context.Context, slog.Record) error) error {
// Add timestamp to all logs
record.AddAttrs(slog.Time("logged_at", time.Now()))
return next(ctx, record)
})
Hook WithAttrs() Method
mdw := slogmulti.NewWithAttrsInlineMiddleware(func(attrs []slog.Attr, next func([]slog.Attr) slog.Handler) slog.Handler{
// Filter out sensitive attributes
filtered := make([]slog.Attr, 0, len(attrs))
for _, attr := range attrs {
if attr.Key != "password" && attr.Key != "token" {
filtered = append(filtered, attr)
}
}
return next(attrs)
})
Hook WithGroup() Method
mdw := slogmulti.NewWithGroupInlineMiddleware(func(name string, next func(string) slog.Handler) slog.Handler{
// Add prefix to group names
prefixedName := "app." + name
return next(name)
})
Complete Inline Middleware
Warning: You should implement your own middleware for complex scenarios.
mdw := slogmulti.NewInlineMiddleware(
func(ctx context.Context, level slog.Level, next func(context.Context, slog.Level) bool) bool{
// Custom logic here
// [...]
return next(ctx, level)
},
func(ctx context.Context, record slog.Record, next func(context.Context, slog.Record) error) error{
// Custom logic here
// [...]
return next(ctx, record)
},
func(attrs []slog.Attr, next func([]slog.Attr) slog.Handler) slog.Handler{
// Custom logic here
// [...]
return next(attrs)
},
func(name string, next func(string) slog.Handler) slog.Handler{
// Custom logic here
// [...]
return next(name)
},
)
π‘ Best Practices
Performance Considerations
- Use Fanout sparingly: Broadcasting to many handlers can impact performance
- Implement sampling: For high-volume logs, consider sampling strategies
- Monitor handler performance: Some handlers (like network-based ones) can be slow
- Use buffering: Consider buffering for network-based handlers
Error Handling
- Always use error recovery: Wrap handlers with
RecoverHandlerError - Implement fallbacks: Use failover patterns for critical logging
- Monitor logging failures: Track when logging fails to identify issues
Security and Privacy
- Redact sensitive data: Use middleware to remove PII and secrets
- Validate log content: Ensure logs don't contain sensitive information
- Use secure connections: For network-based handlers, use TLS
Monitoring and Observability
- Add correlation IDs: Include request IDs in logs for tracing
- Structured logging: Use slog's structured logging features consistently
- Log levels: Use appropriate log levels for different types of information
π€ Contributing
- Ping me on twitter @samuelberthe (DMs, mentions, whatever :))
- Fork the project
- Fix open issues or request new features
Don't hesitate ;)
# Install some dev dependencies
make tools
# Run tests
make test
# or
make watch-test
π€ Contributors
π« Show your support
If this project helped you, please give it a βοΈ on GitHub!
π License
Copyright Β© 2023 Samuel Berthe.
This project is MIT licensed.
Related repositories
Awesome Go, maintained at avelino/awesome-go, groups Go frameworks, libraries, and software into topic categories including Actor Model, Artificial Intelligence, Audio and Music, Authentication and Authorization, Blockchain, Bot Building, and Build Automation. Every linked entry gets a short description instead of a bare URL. The repository carries an MIT license and takes new entries through pull requests reviewed against its own contribution guidelines, which makes it a common bookmark for day-to-day Go development.
golang/go is the open source repository behind The Go Programming Language, holding the compiler, runtime, and standard library source that Go's official binaries are built from. The README frames Go's purpose as building software that is simple, reliable, and efficient, credits the project to thousands of contributors, and notes that this GitHub repo mirrors the canonical Git repository at go.googlesource.com/go.
The Gin Go web framework offers a high-performance, minimalist approach to building web applications and REST APIs in Go. It delivers exceptional speed through its use of httprouter and provides an Express.js-like API for rapid development.
Caddy is an HTTP/1-2-3 web server, written in Go, which provides automatic HTTPS by default, utilizing services like ZeroSSL and Let's Encrypt for public names or a local CA for internal ones. It supports flexible configuration through its native JSON API, a Caddyfile, or various other formats via config adapters. The project highlights a modular architecture for extensibility, runs on multiple platforms without external dependencies, and benefits from Go's memory safety features. The README indicates it has served trillions of requests and managed millions of TLS certificates, scaling to hundreds of thousands of sites in production. Caddy began development in 2014 and was an early adopter of automatic HTTPS by default.
Quick answers
Is samber/slog-multi open source?
Yes β samber/slog-multi ships under the MIT license, which makes its source code freely readable (and, depending on license terms, forkable and reusable). Source: github.com/samber/slog-multi.
What else is in the Backend space?
samber/slog-multi is tracked by TopGit under the Backend category, alongside 12 GitHub-tagged topics. Trending and Topics pages list peer repositories of comparable stars and language.
What is samber/slog-multi?
samber/slog-multi (samber/slog-multi) is a Go project on GitHub. From the project's own README: π¨ Design workflows of slog handlers: pipeline, middleware, fanout, routing, failover, load balancing...
What license does samber/slog-multi use?
samber/slog-multi is released under the MIT license. Always verify the LICENSE file directly on GitHub for the authoritative terms β license strings can be edited out of sync with a project's actual stance.
Where can I see samber/slog-multi in action?
The project maintains a homepage at https://pkg.go.dev/github.com/samber/slog-multi. The README tab on this page also usually contains screenshots and a quickstart.
Where do I read more about samber/slog-multi?
This TopGit page is a snapshot β the READ ME tab shows the project's own README content (links stripped, images preserved). The GitHub repository at github.com/samber/slog-multi is the definitive source.
Why is samber/slog-multi categorized under Backend?
TopGit places samber/slog-multi in the Backend category based on its GitHub topics and description (tagged: "attribute", "error", "errors"). Categories are assigned from real repository metadata, not editorial guesswork.
Read full README in the tab above.
Want a second opinion on slog-multi?
Ask an AI that can read this page β one click and you get its take on slog-multi.