slogmulti

package module
v1.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 4, 2025 License: MIT Imports: 10 Imported by: 204

README ΒΆ

slog-multi: Advanced Handler Composition for Go's Structured Logging (pipelining, fanout, routing, failover...)

tag Go Version GoDoc Build Status Go report Coverage Contributors License

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
  • πŸ”„ 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

See also:

HTTP middlewares:

Loggers:

Log sinks:

πŸš€ 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)
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:

Note: WithAttrs and WithGroup methods of custom middleware must return a new instance, not this.

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

Don't hesitate ;)

# Install some dev dependencies
make tools

# Run tests
make test
# or
make watch-test

πŸ‘€ Contributors

Contributors

πŸ’« Show your support

If this project helped you, please give it a ⭐️ on GitHub!

GitHub Sponsors

πŸ“ License

Copyright Β© 2023 Samuel Berthe.

This project is MIT licensed.

Documentation ΒΆ

Index ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

This section is empty.

Functions ΒΆ

func Failover ΒΆ added in v0.4.0

func Failover() func(...slog.Handler) slog.Handler

Failover creates a failover handler factory function. This function returns a closure that can be used to create failover handlers with different sets of handlers.

Example usage:

handler := slogmulti.Failover()(
    primaryHandler,   // First choice
    secondaryHandler, // Fallback if primary fails
    backupHandler,    // Last resort
)
logger := slog.New(handler)

Returns:

A function that creates FailoverHandler instances with the provided handlers

func Fanout ΒΆ added in v0.3.0

func Fanout(handlers ...slog.Handler) slog.Handler

Fanout creates a new FanoutHandler that distributes records to multiple slog.Handler instances. This function is the primary entry point for creating a multi-handler setup.

Example usage:

handler := slogmulti.Fanout(
    slog.NewJSONHandler(os.Stdout, nil),
    slogdatadog.NewDatadogHandler(...),
)
logger := slog.New(handler)

Args:

handlers: Variable number of slog.Handler instances to distribute logs to

Returns:

A slog.Handler that forwards all operations to the provided handlers

func LevelIs ΒΆ added in v1.5.0

func LevelIs(levels ...slog.Level) func(ctx context.Context, r slog.Record) bool

LevelIs returns a function that checks if the record level is in the given levels. Example usage:

r := slogmulti.Router().
    Add(consoleHandler, slogmulti.LevelIs(slog.LevelInfo)).
    Add(fileHandler, slogmulti.LevelIs(slog.LevelError)).
    Handler()

Args:

levels: The levels to match

Returns:

A function that checks if the record level is in the given levels

func LevelIsNot ΒΆ added in v1.5.0

func LevelIsNot(levels ...slog.Level) func(ctx context.Context, r slog.Record) bool

LevelIsNot returns a function that checks if the record level is not in the given levels. Example usage:

r := slogmulti.Router().
    Add(consoleHandler, slogmulti.LevelIsNot(slog.LevelInfo)).
    Add(fileHandler, slogmulti.LevelIsNot(slog.LevelError)).
    Handler()

Args:

levels: The levels to check

Returns:

A function that checks if the record level is not in the given levels

func MessageContains ΒΆ added in v1.5.0

func MessageContains(part string) func(ctx context.Context, r slog.Record) bool

MessageContains returns a function that checks if the record message contains the given part. Example usage:

r := slogmulti.Router().
    Add(consoleHandler, slogmulti.MessageContains("database error")).
    Add(fileHandler, slogmulti.MessageContains("database error")).
    Handler()

Args:

part: The part to check

Returns:

A function that checks if the record message contains the given part

func MessageIs ΒΆ added in v1.5.0

func MessageIs(msg string) func(ctx context.Context, r slog.Record) bool

MessageIs returns a function that checks if the record message is equal to the given message. Example usage:

r := slogmulti.Router().
    Add(consoleHandler, slogmulti.MessageIs("database error")).
    Add(fileHandler, slogmulti.MessageIs("database error")).
    Handler()

Args:

msg: The message to check

Returns:

A function that checks if the record message is equal to the given message

func MessageIsNot ΒΆ added in v1.5.0

func MessageIsNot(msg string) func(ctx context.Context, r slog.Record) bool

MessageIsNot returns a function that checks if the record message is not equal to the given message. Example usage:

r := slogmulti.Router().
    Add(consoleHandler, slogmulti.MessageIsNot("database error")).
    Add(fileHandler, slogmulti.MessageIsNot("database error")).
    Handler()

Args:

msg: The message to check

Returns:

A function that checks if the record message is not equal to the given message

func MessageNotContains ΒΆ added in v1.5.0

func MessageNotContains(part string) func(ctx context.Context, r slog.Record) bool

MessageNotContains returns a function that checks if the record message does not contain the given part. Example usage:

r := slogmulti.Router().
    Add(consoleHandler, slogmulti.MessageNotContains("database error")).
    Add(fileHandler, slogmulti.MessageNotContains("database error")).
    Handler()

Args:

part: The part to check

Returns:

A function that checks if the record message does not contain the given part

func NewHandleInlineHandler ΒΆ added in v1.3.0

func NewHandleInlineHandler(handleFunc func(ctx context.Context, groups []string, attrs []slog.Attr, record slog.Record) error) slog.Handler

NewHandleInlineHandler is a shortcut to a middleware that implements only the `Handle` method.

func NewInlineHandler ΒΆ added in v1.3.0

func NewInlineHandler(
	enabledFunc func(ctx context.Context, groups []string, attrs []slog.Attr, level slog.Level) bool,
	handleFunc func(ctx context.Context, groups []string, attrs []slog.Attr, record slog.Record) error,
) slog.Handler

NewInlineHandler is a shortcut to a handler that implements all methods.

func Pool ΒΆ added in v0.4.0

func Pool() func(...slog.Handler) slog.Handler

Pool creates a load balancing handler factory function. This function returns a closure that can be used to create pool handlers with different sets of handlers for load balancing.

The pool uses a round-robin strategy with randomization to distribute log records evenly across all available handlers. This is useful for: - Increasing logging throughput by parallelizing handler operations - Providing redundancy by having multiple handlers process the same records - Load balancing across multiple logging destinations

Example usage:

handler := slogmulti.Pool()(
    handler1, // Will receive ~33% of records
    handler2, // Will receive ~33% of records
    handler3, // Will receive ~33% of records
)
logger := slog.New(handler)

Returns:

A function that creates PoolHandler instances with the provided handlers

func RecoverHandlerError ΒΆ added in v1.4.0

func RecoverHandlerError(recovery RecoveryFunc) func(slog.Handler) slog.Handler

RecoverHandlerError creates a middleware that adds error recovery to a slog.Handler. This function returns a closure that can be used to wrap handlers with recovery logic.

The recovery handler provides fault tolerance by: 1. Catching panics from the underlying handler 2. Catching errors returned by the underlying handler 3. Calling the recovery function with the error details 4. Propagating the original error to maintain logging semantics

Example usage:

recovery := slogmulti.RecoverHandlerError(func(ctx context.Context, record slog.Record, err error) {
    fmt.Printf("Logging error: %v\n", err)
})
safeHandler := recovery(riskyHandler)
logger := slog.New(safeHandler)

Args:

recovery: The function to call when an error or panic occurs

Returns:

A function that wraps handlers with recovery logic

func Router ΒΆ added in v0.5.0

func Router() *router

Router creates a new router instance for building conditional log routing. This function is the entry point for creating a routing configuration.

Example usage:

r := slogmulti.Router().
    Add(consoleHandler, slogmulti.LevelIs(slog.LevelInfo)).
    Add(fileHandler, slogmulti.LevelIs(slog.LevelError)).
    Handler()

Returns:

A new router instance ready for configuration

Types ΒΆ

type EnabledInlineMiddleware ΒΆ

type EnabledInlineMiddleware struct {
	// contains filtered or unexported fields
}

func (*EnabledInlineMiddleware) Enabled ΒΆ

func (h *EnabledInlineMiddleware) Enabled(ctx context.Context, level slog.Level) bool

Implements slog.Handler

func (*EnabledInlineMiddleware) Handle ΒΆ

func (h *EnabledInlineMiddleware) Handle(ctx context.Context, record slog.Record) error

Implements slog.Handler

func (*EnabledInlineMiddleware) WithAttrs ΒΆ

func (h *EnabledInlineMiddleware) WithAttrs(attrs []slog.Attr) slog.Handler

Implements slog.Handler

func (*EnabledInlineMiddleware) WithGroup ΒΆ

func (h *EnabledInlineMiddleware) WithGroup(name string) slog.Handler

Implements slog.Handler

type FailoverHandler ΒΆ added in v0.4.0

type FailoverHandler struct {
	// contains filtered or unexported fields
}

FailoverHandler implements a high-availability logging pattern. It attempts to forward log records to handlers in order until one succeeds. This is useful for scenarios where you want primary and backup logging destinations.

@TODO: implement round robin strategy for load balancing across multiple handlers

func (*FailoverHandler) Enabled ΒΆ added in v0.4.0

func (h *FailoverHandler) Enabled(ctx context.Context, l slog.Level) bool

Enabled checks if any of the underlying handlers are enabled for the given log level. This method implements the slog.Handler interface requirement.

The handler is considered enabled if at least one of its child handlers is enabled for the specified level. This ensures that if any handler can process the log, the failover handler will attempt to distribute it.

Args:

ctx: The context for the logging operation
l: The log level to check

Returns:

true if at least one handler is enabled for the level, false otherwise

func (*FailoverHandler) Handle ΒΆ added in v0.4.0

func (h *FailoverHandler) Handle(ctx context.Context, r slog.Record) error

Handle attempts to process a log record using handlers in priority order. This method implements the slog.Handler interface requirement.

This implements a "fail-fast" strategy where the first successful handler prevents further attempts, making it efficient for high-availability scenarios.

Args:

ctx: The context for the logging operation
r: The log record to process

Returns:

nil if any handler successfully processed the record, or the last error encountered

func (*FailoverHandler) WithAttrs ΒΆ added in v0.4.0

func (h *FailoverHandler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs creates a new FailoverHandler with additional attributes added to all child handlers. This method implements the slog.Handler interface requirement.

The method creates new handler instances for each child handler with the additional attributes, ensuring that the attributes are properly propagated to all handlers in the failover chain.

Args:

attrs: The attributes to add to all handlers

Returns:

A new FailoverHandler with the attributes added to all child handlers

func (*FailoverHandler) WithGroup ΒΆ added in v0.4.0

func (h *FailoverHandler) WithGroup(name string) slog.Handler

WithGroup creates a new FailoverHandler with a group name applied to all child handlers. This method implements the slog.Handler interface requirement.

The method follows the same pattern as the standard slog implementation: - If the group name is empty, returns the original handler unchanged - Otherwise, creates new handler instances for each child handler with the group name

Args:

name: The group name to apply to all handlers

Returns:

A new FailoverHandler with the group name applied to all child handlers,
or the original handler if the group name is empty

type FanoutHandler ΒΆ added in v0.3.0

type FanoutHandler struct {
	// contains filtered or unexported fields
}

FanoutHandler distributes log records to multiple slog.Handler instances in parallel. It implements the slog.Handler interface and forwards all logging operations to all registered handlers that are enabled for the given log level.

func (*FanoutHandler) Enabled ΒΆ added in v0.3.0

func (h *FanoutHandler) Enabled(ctx context.Context, l slog.Level) bool

Enabled checks if any of the underlying handlers are enabled for the given log level. This method implements the slog.Handler interface requirement.

The handler is considered enabled if at least one of its child handlers is enabled for the specified level. This ensures that if any handler can process the log, the fanout handler will attempt to distribute it.

Args:

ctx: The context for the logging operation
l: The log level to check

Returns:

true if at least one handler is enabled for the level, false otherwise

func (*FanoutHandler) Handle ΒΆ added in v0.3.0

func (h *FanoutHandler) Handle(ctx context.Context, r slog.Record) error

Handle distributes a log record to all enabled handlers. This method implements the slog.Handler interface requirement.

The method: 1. Iterates through all registered handlers 2. Checks if each handler is enabled for the record's level 3. For enabled handlers, calls their Handle method with a cloned record 4. Collects any errors that occur during handling 5. Returns a combined error if any handlers failed

Note: Each handler receives a cloned record to prevent interference between handlers. This ensures that one handler cannot modify the record for other handlers.

Args:

ctx: The context for the logging operation
r: The log record to distribute

Returns:

An error if any handler failed to process the record, nil otherwise

func (*FanoutHandler) WithAttrs ΒΆ added in v0.3.0

func (h *FanoutHandler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs creates a new FanoutHandler with additional attributes added to all child handlers. This method implements the slog.Handler interface requirement.

The method creates new handler instances for each child handler with the additional attributes, ensuring that the attributes are properly propagated to all handlers in the fanout chain.

Args:

attrs: The attributes to add to all handlers

Returns:

A new FanoutHandler with the attributes added to all child handlers

func (*FanoutHandler) WithGroup ΒΆ added in v0.3.0

func (h *FanoutHandler) WithGroup(name string) slog.Handler

WithGroup creates a new FanoutHandler with a group name applied to all child handlers. This method implements the slog.Handler interface requirement.

The method follows the same pattern as the standard slog implementation: - If the group name is empty, returns the original handler unchanged - Otherwise, creates new handler instances for each child handler with the group name

Args:

name: The group name to apply to all handlers

Returns:

A new FanoutHandler with the group name applied to all child handlers,
or the original handler if the group name is empty

type HandleInlineHandler ΒΆ added in v1.3.0

type HandleInlineHandler struct {
	// contains filtered or unexported fields
}

func (*HandleInlineHandler) Enabled ΒΆ added in v1.3.0

func (h *HandleInlineHandler) Enabled(ctx context.Context, level slog.Level) bool

Implements slog.Handler

func (*HandleInlineHandler) Handle ΒΆ added in v1.3.0

func (h *HandleInlineHandler) Handle(ctx context.Context, record slog.Record) error

Implements slog.Handler

func (*HandleInlineHandler) WithAttrs ΒΆ added in v1.3.0

func (h *HandleInlineHandler) WithAttrs(attrs []slog.Attr) slog.Handler

Implements slog.Handler

func (*HandleInlineHandler) WithGroup ΒΆ added in v1.3.0

func (h *HandleInlineHandler) WithGroup(name string) slog.Handler

Implements slog.Handler

type HandleInlineMiddleware ΒΆ

type HandleInlineMiddleware struct {
	// contains filtered or unexported fields
}

func (*HandleInlineMiddleware) Enabled ΒΆ

func (h *HandleInlineMiddleware) Enabled(ctx context.Context, level slog.Level) bool

Implements slog.Handler

func (*HandleInlineMiddleware) Handle ΒΆ

func (h *HandleInlineMiddleware) Handle(ctx context.Context, record slog.Record) error

Implements slog.Handler

func (*HandleInlineMiddleware) WithAttrs ΒΆ

func (h *HandleInlineMiddleware) WithAttrs(attrs []slog.Attr) slog.Handler

Implements slog.Handler

func (*HandleInlineMiddleware) WithGroup ΒΆ

func (h *HandleInlineMiddleware) WithGroup(name string) slog.Handler

Implements slog.Handler

type HandlerErrorRecovery ΒΆ added in v1.4.0

type HandlerErrorRecovery struct {
	// contains filtered or unexported fields
}

HandlerErrorRecovery wraps a slog.Handler to provide panic and error recovery. It catches both panics and errors from the underlying handler and calls a recovery function to handle them gracefully.

func (*HandlerErrorRecovery) Enabled ΒΆ added in v1.4.0

func (h *HandlerErrorRecovery) Enabled(ctx context.Context, l slog.Level) bool

Enabled checks if the underlying handler is enabled for the given log level. This method implements the slog.Handler interface requirement.

Args:

ctx: The context for the logging operation
l: The log level to check

Returns:

true if the underlying handler is enabled for the level, false otherwise

func (*HandlerErrorRecovery) Handle ΒΆ added in v1.4.0

func (h *HandlerErrorRecovery) Handle(ctx context.Context, record slog.Record) error

Handle processes a log record with error recovery. This method implements the slog.Handler interface requirement.

This ensures that logging errors don't crash the application while still allowing the error to be handled appropriately by the calling code.

Args:

ctx: The context for the logging operation
record: The log record to process

Returns:

The error from the underlying handler (never nil if an error occurred)

func (*HandlerErrorRecovery) WithAttrs ΒΆ added in v1.4.0

func (h *HandlerErrorRecovery) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs creates a new HandlerErrorRecovery with additional attributes. This method implements the slog.Handler interface requirement.

Args:

attrs: The attributes to add to the underlying handler

Returns:

A new HandlerErrorRecovery with the additional attributes

func (*HandlerErrorRecovery) WithGroup ΒΆ added in v1.4.0

func (h *HandlerErrorRecovery) WithGroup(name string) slog.Handler

WithGroup creates a new HandlerErrorRecovery with a group name. This method implements the slog.Handler interface requirement.

The method follows the same pattern as the standard slog implementation: - If the group name is empty, returns the original handler unchanged - Otherwise, creates a new handler with the group name applied to the underlying handler

Args:

name: The group name to apply to the underlying handler

Returns:

A new HandlerErrorRecovery with the group name, or the original handler if the name is empty

type InlineHandler ΒΆ added in v1.3.0

type InlineHandler struct {
	// contains filtered or unexported fields
}

func (*InlineHandler) Enabled ΒΆ added in v1.3.0

func (h *InlineHandler) Enabled(ctx context.Context, level slog.Level) bool

Implements slog.Handler

func (*InlineHandler) Handle ΒΆ added in v1.3.0

func (h *InlineHandler) Handle(ctx context.Context, record slog.Record) error

Implements slog.Handler

func (*InlineHandler) WithAttrs ΒΆ added in v1.3.0

func (h *InlineHandler) WithAttrs(attrs []slog.Attr) slog.Handler

Implements slog.Handler

func (*InlineHandler) WithGroup ΒΆ added in v1.3.0

func (h *InlineHandler) WithGroup(name string) slog.Handler

Implements slog.Handler

type InlineMiddleware ΒΆ

type InlineMiddleware struct {
	// contains filtered or unexported fields
}

func (*InlineMiddleware) Enabled ΒΆ

func (h *InlineMiddleware) Enabled(ctx context.Context, level slog.Level) bool

Implements slog.Handler

func (*InlineMiddleware) Handle ΒΆ

func (h *InlineMiddleware) Handle(ctx context.Context, record slog.Record) error

Implements slog.Handler

func (*InlineMiddleware) WithAttrs ΒΆ

func (h *InlineMiddleware) WithAttrs(attrs []slog.Attr) slog.Handler

Implements slog.Handler

func (*InlineMiddleware) WithGroup ΒΆ

func (h *InlineMiddleware) WithGroup(name string) slog.Handler

Implements slog.Handler

type Middleware ΒΆ

type Middleware func(slog.Handler) slog.Handler

Middleware is a function type that transforms one slog.Handler into another. It follows the standard middleware pattern where a function takes a handler and returns a new handler that wraps the original with additional functionality.

Middleware functions can be used to: - Transform log records (e.g., add timestamps, modify levels) - Filter records based on conditions - Add context or attributes to records - Implement cross-cutting concerns like error recovery or sampling

Example usage:

  gdprMiddleware := NewGDPRMiddleware()
  sink := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{})

	 logger := slog.New(
		slogmulti.
			Pipe(gdprMiddleware).
			// ...
			Handler(sink),
	  )

func NewEnabledInlineMiddleware ΒΆ

func NewEnabledInlineMiddleware(enabledFunc func(ctx context.Context, level slog.Level, next func(context.Context, slog.Level) bool) bool) Middleware

NewEnabledInlineMiddleware is shortcut to a middleware that implements only the `Enable` method.

func NewHandleInlineMiddleware ΒΆ

func NewHandleInlineMiddleware(handleFunc func(ctx context.Context, record slog.Record, next func(context.Context, slog.Record) error) error) Middleware

NewHandleInlineMiddleware is a shortcut to a middleware that implements only the `Handle` method.

func NewInlineMiddleware ΒΆ

func NewInlineMiddleware(
	enabledFunc func(ctx context.Context, level slog.Level, next func(context.Context, slog.Level) bool) bool,
	handleFunc func(ctx context.Context, record slog.Record, next func(context.Context, slog.Record) error) error,
	withAttrsFunc func(attrs []slog.Attr, next func([]slog.Attr) slog.Handler) slog.Handler,
	withGroupFunc func(name string, next func(string) slog.Handler) slog.Handler,
) Middleware

NewInlineMiddleware is a shortcut to a middleware that implements all methods.

func NewWithAttrsInlineMiddleware ΒΆ

func NewWithAttrsInlineMiddleware(withAttrsFunc func(attrs []slog.Attr, next func([]slog.Attr) slog.Handler) slog.Handler) Middleware

NewWithAttrsInlineMiddleware is a shortcut to a middleware that implements only the `WithAttrs` method.

func NewWithGroupInlineMiddleware ΒΆ

func NewWithGroupInlineMiddleware(withGroupFunc func(name string, next func(string) slog.Handler) slog.Handler) Middleware

NewWithGroupInlineMiddleware is a shortcut to a middleware that implements only the `WithAttrs` method.

type PipeBuilder ΒΆ

type PipeBuilder struct {
	// contains filtered or unexported fields
}

PipeBuilder provides a fluent API for building middleware chains. It allows you to compose multiple middleware functions that will be applied to log records in the order they are added (last-in, first-out).

func Pipe ΒΆ

func Pipe(middlewares ...Middleware) *PipeBuilder

Pipe creates a new PipeBuilder with the provided middleware functions. This function is the entry point for building middleware chains.

Middleware functions are applied in reverse order (last-in, first-out), which means the last middleware added will be the first one applied to incoming records. This allows for intuitive composition where you can think of the chain as "transform A, then transform B, then send to handler".

Example usage:

handler := slogmulti.Pipe(
    RewriteLevel(slog.LevelWarn, slog.LevelInfo),
    RewriteMessage("prefix: %s"),
    RedactPII(),
).Handler(finalHandler)

Args:

middlewares: Variable number of middleware functions to chain together

Returns:

A new PipeBuilder instance ready for further configuration

func (*PipeBuilder) Handler ΒΆ

func (h *PipeBuilder) Handler(handler slog.Handler) slog.Handler

Handler creates a slog.Handler by applying all middleware to the provided handler. This method finalizes the middleware chain and returns a handler that can be used with slog.New().

This LIFO approach ensures that the middleware chain is applied in the intuitive order: the first middleware in the chain is applied first to incoming records.

Args:

handler: The final slog.Handler that will receive the transformed records

Returns:

A slog.Handler that applies all middleware transformations before forwarding to the final handler

func (*PipeBuilder) Pipe ΒΆ

func (h *PipeBuilder) Pipe(middleware Middleware) *PipeBuilder

Pipe adds an additional middleware to the chain. This method provides a fluent API for building middleware chains incrementally.

Args:

middleware: The middleware function to add to the chain

Returns:

The PipeBuilder instance for method chaining

type PoolHandler ΒΆ added in v0.4.0

type PoolHandler struct {
	// contains filtered or unexported fields
}

PoolHandler implements a load balancing strategy for logging handlers. It distributes log records across multiple handlers using a round-robin approach with randomization to ensure even distribution and prevent hot-spotting.

func (*PoolHandler) Enabled ΒΆ added in v0.4.0

func (h *PoolHandler) Enabled(ctx context.Context, l slog.Level) bool

Enabled checks if any of the underlying handlers are enabled for the given log level. This method implements the slog.Handler interface requirement.

The handler is considered enabled if at least one of its child handlers is enabled for the specified level. This ensures that if any handler can process the log, the pool handler will attempt to distribute it.

Args:

ctx: The context for the logging operation
l: The log level to check

Returns:

true if at least one handler is enabled for the level, false otherwise

func (*PoolHandler) Handle ΒΆ added in v0.4.0

func (h *PoolHandler) Handle(ctx context.Context, r slog.Record) error

Handle distributes a log record to a handler selected using round-robin with randomization. This method implements the slog.Handler interface requirement.

This approach ensures even distribution of load while providing fault tolerance through the failover behavior when a handler is unavailable.

Args:

ctx: The context for the logging operation
r: The log record to distribute

Returns:

nil if any handler successfully processed the record, or the last error encountered

func (*PoolHandler) WithAttrs ΒΆ added in v0.4.0

func (h *PoolHandler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs creates a new PoolHandler with additional attributes added to all child handlers. This method implements the slog.Handler interface requirement.

The method creates new handler instances for each child handler with the additional attributes, ensuring that the attributes are properly propagated to all handlers in the pool.

Args:

attrs: The attributes to add to all handlers

Returns:

A new PoolHandler with the attributes added to all child handlers

func (*PoolHandler) WithGroup ΒΆ added in v0.4.0

func (h *PoolHandler) WithGroup(name string) slog.Handler

WithGroup creates a new PoolHandler with a group name applied to all child handlers. This method implements the slog.Handler interface requirement.

The method follows the same pattern as the standard slog implementation: - If the group name is empty, returns the original handler unchanged - Otherwise, creates new handler instances for each child handler with the group name

Args:

name: The group name to apply to all handlers

Returns:

A new PoolHandler with the group name applied to all child handlers,
or the original handler if the group name is empty

type RecoveryFunc ΒΆ added in v1.4.0

type RecoveryFunc func(ctx context.Context, record slog.Record, err error)

RecoveryFunc is a callback function that handles errors and panics from logging handlers. It receives the context, the log record that caused the error, and the error itself. This function can be used to log the error, send alerts, or perform any other error handling logic without affecting the main application flow.

type RoutableHandler ΒΆ added in v0.5.0

type RoutableHandler struct {
	// contains filtered or unexported fields
}

RoutableHandler wraps a slog.Handler with conditional matching logic. It only forwards records to the underlying handler if all predicates return true. This enables sophisticated routing scenarios like level-based or attribute-based routing.

@TODO: implement round robin strategy for load balancing across multiple handlers

func (*RoutableHandler) Enabled ΒΆ added in v0.5.0

func (h *RoutableHandler) Enabled(ctx context.Context, l slog.Level) bool

Enabled checks if the underlying handler is enabled for the given log level. This method implements the slog.Handler interface requirement.

Args:

ctx: The context for the logging operation
l: The log level to check

Returns:

true if the underlying handler is enabled for the level, false otherwise

func (*RoutableHandler) Handle ΒΆ added in v0.5.0

func (h *RoutableHandler) Handle(ctx context.Context, r slog.Record) error

Handle processes a log record if all predicates return true. This method implements the slog.Handler interface requirement.

Args:

ctx: The context for the logging operation
r: The log record to process

Returns:

An error if the underlying handler failed to process the record, nil otherwise

func (*RoutableHandler) WithAttrs ΒΆ added in v0.5.0

func (h *RoutableHandler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs creates a new RoutableHandler with additional attributes. This method implements the slog.Handler interface requirement.

The method properly handles attribute accumulation within the current group context, ensuring that attributes are correctly applied to records when they are processed.

Args:

attrs: The attributes to add to the handler

Returns:

A new RoutableHandler with the additional attributes

func (*RoutableHandler) WithGroup ΒΆ added in v0.5.0

func (h *RoutableHandler) WithGroup(name string) slog.Handler

WithGroup creates a new RoutableHandler with a group name. This method implements the slog.Handler interface requirement.

The method follows the same pattern as the standard slog implementation: - If the group name is empty, returns the original handler unchanged - Otherwise, creates a new handler with the group name added to the group hierarchy

Args:

name: The group name to apply to the handler

Returns:

A new RoutableHandler with the group name, or the original handler if the name is empty

type WithAttrsInlineMiddleware ΒΆ

type WithAttrsInlineMiddleware struct {
	// contains filtered or unexported fields
}

func (*WithAttrsInlineMiddleware) Enabled ΒΆ

func (h *WithAttrsInlineMiddleware) Enabled(ctx context.Context, level slog.Level) bool

Implements slog.Handler

func (*WithAttrsInlineMiddleware) Handle ΒΆ

func (h *WithAttrsInlineMiddleware) Handle(ctx context.Context, record slog.Record) error

Implements slog.Handler

func (*WithAttrsInlineMiddleware) WithAttrs ΒΆ

func (h *WithAttrsInlineMiddleware) WithAttrs(attrs []slog.Attr) slog.Handler

Implements slog.Handler

func (*WithAttrsInlineMiddleware) WithGroup ΒΆ

func (h *WithAttrsInlineMiddleware) WithGroup(name string) slog.Handler

Implements slog.Handler

type WithGroupInlineMiddleware ΒΆ

type WithGroupInlineMiddleware struct {
	// contains filtered or unexported fields
}

func (*WithGroupInlineMiddleware) Enabled ΒΆ

func (h *WithGroupInlineMiddleware) Enabled(ctx context.Context, level slog.Level) bool

Implements slog.Handler

func (*WithGroupInlineMiddleware) Handle ΒΆ

func (h *WithGroupInlineMiddleware) Handle(ctx context.Context, record slog.Record) error

Implements slog.Handler

func (*WithGroupInlineMiddleware) WithAttrs ΒΆ

func (h *WithGroupInlineMiddleware) WithAttrs(attrs []slog.Attr) slog.Handler

Implements slog.Handler

func (*WithGroupInlineMiddleware) WithGroup ΒΆ

func (h *WithGroupInlineMiddleware) WithGroup(name string) slog.Handler

Implements slog.Handler

Directories ΒΆ

Path Synopsis
examples
failover Module
fanout Module
pipe Module
pool Module
router Module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL