slogmulti

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Apr 10, 2023 License: MIT Imports: 4 Imported by: 175

README ΒΆ

slog: Handler chain, fanout, fallback...

tag Go Version GoDoc Build Status Go report Coverage Contributors License

Design workflows of slog handlers:

  • fanout: distribute log.Record to multiple slog.Handler in parallel
  • pipeline: rewrite log.Record on the fly (eg: for privacy reason)
  • fallback: forward log.Record to the first available slog.Handler

Here a simple workflow with both pipeline and fanout:

workflow example with pipeline and fanout

See also:

πŸš€ Install

go get github.com/samber/slog-multi

Compatibility: go >= 1.20.1

This library is v0 and follows SemVer strictly. On slog final release (go 1.21), this library will go v1.

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

πŸ’‘ Usage

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

Fanout: slogmulti.Multi()

Distribute logs to multiple slog.Handler in parallel.

import (
    slogmulti "github.com/samber/slog-multi"
    "golang.org/x/exp/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.NewMultiHandler(
            slog.HandlerOptions{}.NewJSONHandler(logstash),  // first handler: logstash over tcp
            slog.HandlerOptions{}.NewTextHandler(stderr),    // second handler: stderr
            // ...
        ),
    )

    logger.
        With(
            slog.Group("user",
                slog.String("id", "user-123"),
                slog.Time("created_at", time.Now().AddDate(0, 0, -1)),
            ),
        ).
        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"
}
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.HandlerOptions{}.NewJSONHandler(os.Stderr)

    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().AddDate(0, 0, -1)),
            ),
        ).
        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)
    },
)
Fallback: slogmulti.Either()

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"
    "golang.org/x/exp/slog"
)

func main() {
	// ncat -l 1000 -k
	// ncat -l 1001 -k
	// ncat -l 1002 -k

    // list AZs
	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.Either(
			slog.HandlerOptions{}.NewJSONHandler(logstash1),    // send to this instance first
			slog.HandlerOptions{}.NewJSONHandler(logstash2),    // then this instance in case of failure
			slog.HandlerOptions{}.NewJSONHandler(logstash3),    // 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().AddDate(0, 0, -1)),
			),
		).
		With("environment", "dev").
		With("error", fmt.Errorf("an error")).
		Error("A message")
}

🀝 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 Either ΒΆ added in v0.2.0

func Either(handlers ...slog.Handler) slog.Handler

func NewMultiHandler ΒΆ

func NewMultiHandler(handlers ...slog.Handler) slog.Handler

Types ΒΆ

type EitherHandler ΒΆ added in v0.2.0

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

@TODO: implement round robin strategy ?

func (*EitherHandler) Enabled ΒΆ added in v0.2.0

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

func (*EitherHandler) Handle ΒΆ added in v0.2.0

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

func (*EitherHandler) WithAttrs ΒΆ added in v0.2.0

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

func (*EitherHandler) WithGroup ΒΆ added in v0.2.0

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

type EnabledInlineMiddleware ΒΆ

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

func (*EnabledInlineMiddleware) Enabled ΒΆ

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

func (*EnabledInlineMiddleware) Handle ΒΆ

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

func (*EnabledInlineMiddleware) WithAttrs ΒΆ

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

func (*EnabledInlineMiddleware) WithGroup ΒΆ

func (h *EnabledInlineMiddleware) WithGroup(name string) 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

func (*HandleInlineMiddleware) Handle ΒΆ

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

func (*HandleInlineMiddleware) WithAttrs ΒΆ

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

func (*HandleInlineMiddleware) WithGroup ΒΆ

func (h *HandleInlineMiddleware) WithGroup(name string) 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

func (*InlineMiddleware) Handle ΒΆ

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

func (*InlineMiddleware) WithAttrs ΒΆ

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

func (*InlineMiddleware) WithGroup ΒΆ

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

type Middleware ΒΆ

type Middleware func(slog.Handler) slog.Handler

func NewEnabledInlineMiddleware ΒΆ

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

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

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

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

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

Shortcut to a middleware that implements only the `WithAttrs` method.

type MultiHandler ΒΆ

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

func (*MultiHandler) Enabled ΒΆ

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

func (*MultiHandler) Handle ΒΆ

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

@TODO: return multiple errors ?

func (*MultiHandler) WithAttrs ΒΆ

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

func (*MultiHandler) WithGroup ΒΆ

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

type PipeBuilder ΒΆ

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

func Pipe ΒΆ

func Pipe(middlewares ...Middleware) *PipeBuilder

func (*PipeBuilder) Handler ΒΆ

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

func (*PipeBuilder) Pipe ΒΆ

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

type WithAttrsInlineMiddleware ΒΆ

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

func (*WithAttrsInlineMiddleware) Enabled ΒΆ

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

func (*WithAttrsInlineMiddleware) Handle ΒΆ

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

func (*WithAttrsInlineMiddleware) WithAttrs ΒΆ

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

func (*WithAttrsInlineMiddleware) WithGroup ΒΆ

func (h *WithAttrsInlineMiddleware) WithGroup(name string) 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

func (*WithGroupInlineMiddleware) Handle ΒΆ

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

func (*WithGroupInlineMiddleware) WithAttrs ΒΆ

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

func (*WithGroupInlineMiddleware) WithGroup ΒΆ

func (h *WithGroupInlineMiddleware) WithGroup(name string) 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