multi

package
v0.0.6 Latest Latest
Warning

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

Go to latest
Published: Aug 15, 2024 License: MIT Imports: 10 Imported by: 0

README ΒΆ

πŸš€ Install

go get github.com/samber/slog-multi

Compatibility: go >= 1.21

No breaking changes will be made to exported APIs before v2.0.0.

⚠️ Use this library carefully, log processing can be very costly (!)

πŸ’‘ Usage

GoDoc: https://pkg.go.dev/github.com/samber/slog-multi

Broadcast: slogmulti.Fanout()

Distribute logs to multiple slog.Handler in parallel.

import (
    slogmulti "github.com/samber/slog-multi"
    "log/slog"
)

func main() {
    logstash, _ := net.Dial("tcp", "logstash.acme:4242")    // use github.com/netbrain/goautosocket for auto-reconnect
    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
            // ...
        ),
    )

    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 in parallel.

import (
    slogmulti "github.com/samber/slog-multi"
	slogslack "github.com/samber/slog-slack"
    "log/slog"
)

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()

	logger := slog.New(
		slogmulti.Router().
			Add(slackChannelUS, recordMatchRegion("us")).
			Add(slackChannelEU, recordMatchRegion("eu")).
			Add(slackChannelAPAC, recordMatchRegion("apac")).
			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
	}
}

Failover: slogmulti.Failover()

List multiple targets for a slog.Record instead of retrying on the same unavailable log management system.

import (
	"net"
    slogmulti "github.com/samber/slog-multi"
    "log/slog"
)


func main() {
	// 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),    // send to this instance first
			slog.HandlerOptions{}.NewJSONHandler(logstash2, nil),    // then this instance in case of failure
			slog.HandlerOptions{}.NewJSONHandler(logstash3, nil),    // and finally this instance in case of double failure
		),
	)

	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")
}

Load balancing: slogmulti.Pool()

Increase log bandwidth by sending log.Record to a pool of slog.Handler.

import (
	"net"
    slogmulti "github.com/samber/slog-multi"
    "log/slog"
)

func main() {
	// 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
			slog.HandlerOptions{}.NewJSONHandler(logstash1, nil),
			slog.HandlerOptions{}.NewJSONHandler(logstash2, nil),
			slog.HandlerOptions{}.NewJSONHandler(logstash3, nil),
		),
	)

	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")
}

Chaining: slogmulti.Pipe()

Rewrite log.Record on the fly (eg: for privacy reason).

func main() {
    // first middleware: format go `error` type into an object {error: "*myCustomErrorType", message: "could not reach https://a.b/c"}
    errorFormattingMiddleware := slogmulti.NewHandleInlineMiddleware(errorFormattingMiddleware)

    // second middleware: remove PII
    gdprMiddleware := NewGDPRMiddleware()

    // 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":{
        "id":"*******",
        "email":"*******",
        "created_at":"*******"
    },
    "environment":"dev",
    "foo":"bar",
    "error":{
        "type":"*myCustomErrorType",
        "message":"an error"
    }
}
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, instead of this.

Inline middleware

An "inline middleware" (aka. lambda), is a shortcut to middleware implementation, that hooks a single method and proxies others.

// hook `logger.Enabled` method
mdw := slogmulti.NewEnabledInlineMiddleware(func(ctx context.Context, level slog.Level, next func(context.Context, slog.Level) bool) bool{
    // [...]
    return next(ctx, level)
})
// hook `logger.Handle` method
mdw := slogmulti.NewHandleInlineMiddleware(func(ctx context.Context, record slog.Record, next func(context.Context, slog.Record) error) error {
    // [...]
    return next(ctx, record)
})
// hook `logger.WithAttrs` method
mdw := slogmulti.NewWithAttrsInlineMiddleware(func(attrs []slog.Attr, next func([]slog.Attr) slog.Handler) slog.Handler{
    // [...]
    return next(attrs)
})
// hook `logger.WithGroup` method
mdw := slogmulti.NewWithGroupInlineMiddleware(func(name string, next func(string) slog.Handler) slog.Handler{
    // [...]
    return next(name)
})

A super inline middleware that hooks all methods.

Warning: you would rather implement your own middleware.

mdw := slogmulti.NewInlineMiddleware(
    func(ctx context.Context, level slog.Level, next func(context.Context, slog.Level) bool) bool{
        // [...]
        return next(ctx, level)
    },
    func(ctx context.Context, record slog.Record, next func(context.Context, slog.Record) error) error{
        // [...]
        return next(ctx, record)
    },
    func(attrs []slog.Attr, next func([]slog.Attr) slog.Handler) slog.Handler{
        // [...]
        return next(attrs)
    },
    func(name string, next func(string) slog.Handler) slog.Handler{
        // [...]
        return next(name)
    },
)

🀝 Contributing

Don't hesitate ;)

# Install some dev dependencies
make tools

# Run tests
make test
# or
make watch-test

πŸ‘€ Contributors

Contributors

πŸ’« Show your support

Give a ⭐️ if this project helped you!

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 ΒΆ

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

Failover forward record to the first available slog.Handler

func Fanout ΒΆ

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

Fanout distributes records to multiple slog.Handler in parallel

func Pool ΒΆ

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

Pool balances records between multiple slog.Handler in order to increase bandwidth. Uses a round robin strategy.

func Router ΒΆ

func Router() *router

Router forward record to all matching slog.Handler.

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 ΒΆ

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

@TODO: implement round robin strategy ?

func (*FailoverHandler) Enabled ΒΆ

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

Implements slog.Handler

func (*FailoverHandler) Handle ΒΆ

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

Implements slog.Handler

func (*FailoverHandler) WithAttrs ΒΆ

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

Implements slog.Handler

func (*FailoverHandler) WithGroup ΒΆ

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

Implements slog.Handler

type FanoutHandler ΒΆ

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

func (*FanoutHandler) Enabled ΒΆ

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

Implements slog.Handler

func (*FanoutHandler) Handle ΒΆ

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

Implements slog.Handler

func (*FanoutHandler) WithAttrs ΒΆ

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

Implements slog.Handler

func (*FanoutHandler) WithGroup ΒΆ

func (h *FanoutHandler) 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 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 defines the handler used by slog.Handler as return value.

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
}

Pipe defines a chain of Middleware.

func Pipe ΒΆ

func Pipe(middlewares ...Middleware) *PipeBuilder

Pipe builds a chain of Middleware. Eg: rewrite log.Record on the fly for privacy reason.

func (*PipeBuilder) Handler ΒΆ

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

Implements slog.Handler

func (*PipeBuilder) Pipe ΒΆ

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

Implements slog.Handler

type PoolHandler ΒΆ

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

func (*PoolHandler) Enabled ΒΆ

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

Implements slog.Handler

func (*PoolHandler) Handle ΒΆ

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

Implements slog.Handler

func (*PoolHandler) WithAttrs ΒΆ

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

Implements slog.Handler

func (*PoolHandler) WithGroup ΒΆ

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

Implements slog.Handler

type RoutableHandler ΒΆ

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

@TODO: implement round robin strategy ?

func (*RoutableHandler) Enabled ΒΆ

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

Implements slog.Handler

func (*RoutableHandler) Handle ΒΆ

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

Implements slog.Handler

func (*RoutableHandler) WithAttrs ΒΆ

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

Implements slog.Handler

func (*RoutableHandler) WithGroup ΒΆ

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

Implements slog.Handler

type TCPClient ΒΆ added in v0.0.4

type TCPClient struct {
	*net.TCPConn
	// contains filtered or unexported fields
}

func Dial ΒΆ added in v0.0.4

func Dial(network, addr string) (*TCPClient, error)

func DialTCP ΒΆ added in v0.0.4

func DialTCP(network string, laddr, raddr *net.TCPAddr) (*TCPClient, error)

func (*TCPClient) SetMaxRetries ΒΆ added in v0.0.4

func (c *TCPClient) SetMaxRetries(maxRetries int)

func (*TCPClient) SetRetryInterval ΒΆ added in v0.0.4

func (c *TCPClient) SetRetryInterval(retryInterval time.Duration)

func (*TCPClient) Write ΒΆ added in v0.0.4

func (c *TCPClient) Write(b []byte) (int, error)

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

Jump to

Keyboard shortcuts

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