From b834b2b4d6d2ac62c382784d441e964adb678877 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Sat, 25 Nov 2023 17:24:05 -0700 Subject: [PATCH] Add slog-multi middleware methods --- README.md | 11 +++++++++++ append_handler.go | 26 ++++++++++++++++++++++---- helpers.go | 4 ++-- ignore_handler.go | 18 ++++++++++++++++++ increment_handler.go | 18 ++++++++++++++++++ overwrite_handler.go | 20 +++++++++++++++++++- 6 files changed, 90 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9d71320..b978eda 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,17 @@ var overwriter = slogdedup.NewOverwriteHandler(slog.NewJSONHandler(os.Stdout, ni Named imports are unaffected. ## Other Details +### slog-multi Middleware +This library has convenience methods that allow it to interoperate with [github.com/samber/slog-multi](https://github.com/samber/slog-multi), +in order to easily setup slog workflows such as pipelines, fanout, routing, failover, etc. +```go +slog.SetDefault(slog.New(slogmulti. + Pipe(slogcontext.NewMiddleware(&slogcontext.HandlerOptions{})). + Pipe(slogdedup.NewOverwriteMiddleware(&slogdedup.OverwriteHandlerOptions{})). + Handler(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{})), +)) +``` + ### Overwrite Handler Using an overwrite handler allows a slightly different style of logging that is less verbose. As an application moves deeper into domain functions, it is common that additional details or knowledge is uncovered. By overwriting keys with better and more explanatory values as you go, the final log lines are often easier to read and more informative. diff --git a/append_handler.go b/append_handler.go index 8615256..480ce4d 100644 --- a/append_handler.go +++ b/append_handler.go @@ -30,6 +30,24 @@ type AppendHandler struct { var _ slog.Handler = &AppendHandler{} // Assert conformance with interface +// NewAppendMiddleware creates an AppendHandler slog.Handler middleware +// that conforms to [github.com/samber/slog-multi.Middleware] interface. +// It can be used with slogmulti methods such as Pipe to easily setup a pipeline of slog handlers: +// +// slog.SetDefault(slog.New(slogmulti. +// Pipe(slogcontext.NewMiddleware(&slogcontext.HandlerOptions{})). +// Pipe(slogdedup.NewAppendMiddleware(&slogdedup.AppendHandlerOptions{})). +// Handler(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{})), +// )) +func NewAppendMiddleware(options *AppendHandlerOptions) func(slog.Handler) slog.Handler { + return func(next slog.Handler) slog.Handler { + return NewAppendHandler( + next, + options, + ) + } +} + // NewAppendHandler creates a AppendHandler slog.Handler middleware that will deduplicate all attributes and // groups by creating a slice/array whenever there is more than one attribute with the same key. // It passes the final record and attributes off to the next handler when finished. @@ -110,7 +128,7 @@ func (h *AppendHandler) createAttrTree(uniq *b.Tree[string, any], goas []*groupO // If a group is encountered, create a subtree for that group and all groupOrAttrs after it if goas[0].group != "" { - if key, ok := h.getKey(goas[0].group, depth); ok { + if key, keep := h.getKey(goas[0].group, depth); keep { uniqGroup := b.TreeNew[string, any](h.keyCompare) h.createAttrTree(uniqGroup, goas[1:], depth+1) // Ignore empty groups, otherwise put subtree into the map @@ -142,7 +160,7 @@ func (h *AppendHandler) createAttrTree(uniq *b.Tree[string, any], goas []*groupO // Since attributes are ordered from oldest to newest, it creates a slice whenever it detects the key already exists, // appending the new attribute, then overwriting the key with that slice. func (h *AppendHandler) resolveValues(uniq *b.Tree[string, any], attrs []slog.Attr, depth int) { - var ok bool + var keep bool for _, a := range attrs { a.Value = a.Value.Resolve() if a.Equal(slog.Attr{}) { @@ -150,8 +168,8 @@ func (h *AppendHandler) resolveValues(uniq *b.Tree[string, any], attrs []slog.At } // Default situation: resolve the key and put it into the map - a.Key, ok = h.getKey(a.Key, depth) - if !ok { + a.Key, keep = h.getKey(a.Key, depth) + if !keep { continue } diff --git a/helpers.go b/helpers.go index 132b088..17d3dda 100644 --- a/helpers.go +++ b/helpers.go @@ -95,8 +95,8 @@ type appended []any // buildAttrs converts the deduplicated map back into an attribute array, // with any subtrees converted into slog.Group's func buildAttrs(uniq *b.Tree[string, any]) []slog.Attr { - en, err := uniq.SeekFirst() - if err != nil { + en, emptyErr := uniq.SeekFirst() + if emptyErr != nil { return nil // Empty (btree only returns an error when empty) } defer en.Close() diff --git a/ignore_handler.go b/ignore_handler.go index 3eaa3c0..626ffcb 100644 --- a/ignore_handler.go +++ b/ignore_handler.go @@ -30,6 +30,24 @@ type IgnoreHandler struct { var _ slog.Handler = &IgnoreHandler{} // Assert conformance with interface +// NewIgnoreMiddleware creates an IgnoreHandler slog.Handler middleware +// that conforms to [github.com/samber/slog-multi.Middleware] interface. +// It can be used with slogmulti methods such as Pipe to easily setup a pipeline of slog handlers: +// +// slog.SetDefault(slog.New(slogmulti. +// Pipe(slogcontext.NewMiddleware(&slogcontext.HandlerOptions{})). +// Pipe(slogdedup.NewIgnoreMiddleware(&slogdedup.IgnoreHandlerOptions{})). +// Handler(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{})), +// )) +func NewIgnoreMiddleware(options *IgnoreHandlerOptions) func(slog.Handler) slog.Handler { + return func(next slog.Handler) slog.Handler { + return NewIgnoreHandler( + next, + options, + ) + } +} + // NewIgnoreHandler creates a IgnoreHandler slog.Handler middleware that will deduplicate all attributes and // groups by ignoring any newer attributes or groups with the same string key as an older attribute. // It passes the final record and attributes off to the next handler when finished. diff --git a/increment_handler.go b/increment_handler.go index 7c2d42a..f76d66e 100644 --- a/increment_handler.go +++ b/increment_handler.go @@ -32,6 +32,24 @@ type IncrementHandler struct { var _ slog.Handler = &IncrementHandler{} // Assert conformance with interface +// NewIncrementMiddleware creates an IncrementHandler slog.Handler middleware +// that conforms to [github.com/samber/slog-multi.Middleware] interface. +// It can be used with slogmulti methods such as Pipe to easily setup a pipeline of slog handlers: +// +// slog.SetDefault(slog.New(slogmulti. +// Pipe(slogcontext.NewMiddleware(&slogcontext.HandlerOptions{})). +// Pipe(slogdedup.NewIncrementMiddleware(&slogdedup.IncrementHandlerOptions{})). +// Handler(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{})), +// )) +func NewIncrementMiddleware(options *IncrementHandlerOptions) func(slog.Handler) slog.Handler { + return func(next slog.Handler) slog.Handler { + return NewIncrementHandler( + next, + options, + ) + } +} + // NewIncrementHandler creates a IncrementHandler slog.Handler middleware that will deduplicate all attributes and // groups by incrementing/modifying their key names. // It passes the final record and attributes off to the next handler when finished. diff --git a/overwrite_handler.go b/overwrite_handler.go index 61a602c..a60d83f 100644 --- a/overwrite_handler.go +++ b/overwrite_handler.go @@ -30,7 +30,25 @@ type OverwriteHandler struct { var _ slog.Handler = &OverwriteHandler{} // Assert conformance with interface -// NewOverwriteHandler creates a OverwriteHandler slog.Handler middleware that will deduplicate all attributes and +// NewOverwriteMiddleware creates an OverwriteHandler slog.Handler middleware +// that conforms to [github.com/samber/slog-multi.Middleware] interface. +// It can be used with slogmulti methods such as Pipe to easily setup a pipeline of slog handlers: +// +// slog.SetDefault(slog.New(slogmulti. +// Pipe(slogcontext.NewMiddleware(&slogcontext.HandlerOptions{})). +// Pipe(slogdedup.NewOverwriteMiddleware(&slogdedup.OverwriteHandlerOptions{})). +// Handler(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{})), +// )) +func NewOverwriteMiddleware(options *OverwriteHandlerOptions) func(slog.Handler) slog.Handler { + return func(next slog.Handler) slog.Handler { + return NewOverwriteHandler( + next, + options, + ) + } +} + +// NewOverwriteHandler creates an OverwriteHandler slog.Handler middleware that will deduplicate all attributes and // groups by overwriting any older attributes or groups with the same string key. // It passes the final record and attributes off to the next handler when finished. // If opts is nil, the default options are used.