From a993959de7c268086eb2cd513a305232f6bec5eb Mon Sep 17 00:00:00 2001 From: "T.v.Dein" Date: Fri, 19 Jan 2024 13:40:00 +0100 Subject: [PATCH] finalized tests, made .With() work to create sub-loggers (#2) Co-authored-by: Thomas von Dein --- Makefile | 5 +++ handler.go | 81 ++++++++++++++++++++++++++++++++++++-------- handler_test.go | 89 ++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 152 insertions(+), 23 deletions(-) diff --git a/Makefile b/Makefile index 614d193..b134d2f 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ # # no need to modify anything below +VERSION = $(shell grep VERSION handler.go | head -1 | cut -d '"' -f2) + all: buildlocal buildlocal: @@ -30,3 +32,6 @@ goupdate: lint: golangci-lint run -p bugs -p unused + +release: buildlocal test + @echo gh release create v$(VERSION) --generate-notes releases/* diff --git a/handler.go b/handler.go index 49c0869..465d0fb 100644 --- a/handler.go +++ b/handler.go @@ -15,18 +15,23 @@ import ( "github.com/fatih/color" ) +const VERSION = "0.1.0" + // We use RFC datestring by default const DefaultTimeFormat = "2006-01-02T03:04.05 MST" // Default log level is INFO: const defaultLevel = slog.LevelInfo +// holds attributes added with logger.With() +type attributes map[string]interface{} + type Handler struct { writer io.Writer mu *sync.Mutex level slog.Leveler groups []string - attrs string + attrs attributes timeFormat string replaceAttr func(groups []string, a slog.Attr) slog.Attr addSource bool @@ -49,6 +54,7 @@ type Options struct { ReplaceAttr func(groups []string, a slog.Attr) slog.Attr TimeFormat string AddSource bool + NoColor bool } func (h *Handler) Handle(ctx context.Context, r slog.Record) error { @@ -71,7 +77,15 @@ func (h *Handler) Handle(ctx context.Context, r slog.Record) error { return true }) - tree := h.attrs + tree := "" + + if len(h.attrs) > 0 { + bytetree, err := yaml.Marshal(h.attrs) + if err != nil { + return err + } + tree = h.Postprocess(bytetree) + } if len(fields) > 0 { bytetree, err := yaml.Marshal(&fields) @@ -79,7 +93,7 @@ func (h *Handler) Handle(ctx context.Context, r slog.Record) error { return err } - tree = h.Postprocess(bytetree) + tree += h.Postprocess(bytetree) } timeStr := "" @@ -144,6 +158,10 @@ func NewHandler(out io.Writer, opts *Options) *Handler { h.timeFormat = DefaultTimeFormat } + if opts.NoColor { + color.NoColor = true + } + return h } @@ -153,24 +171,59 @@ func (h *Handler) Enabled(_ context.Context, l slog.Level) bool { } // attributes plus attrs. +func (h *Handler) appendAttr(wa map[string]interface{}, a slog.Attr) { + a.Value = a.Value.Resolve() + + if a.Value.Kind() == slog.KindGroup { + attrs := a.Value.Group() + name := "" + if len(attrs) == 0 { + return + } + + if a.Key != "" { + name = a.Key + h.groups = append(h.groups, a.Key) + } + + innerwa := make(map[string]interface{}) + for _, a := range attrs { + h.appendAttr(innerwa, a) + } + wa[name] = innerwa + + if a.Key != "" { + h.groups = h.groups[:len(h.groups)-1] + } + + return + } + + if h.replaceAttr != nil { + a = h.replaceAttr(h.groups, a) + } + + if !a.Equal(slog.Attr{}) { + wa[a.Key] = a.Value.Any() + } +} + +// sub logger is to be created, possibly with attrs, add them to h.attrs func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler { if len(attrs) == 0 { return h } - fields := make(map[string]interface{}, len(attrs)) - for _, a := range attrs { - fields[a.Key] = a.Value.Any() - } - - bytetree, err := yaml.Marshal(&fields) - if err != nil { - panic(err) - } - h2 := h.clone() - h2.attrs += string(bytetree) + wa := make(map[string]interface{}) + + for _, a := range attrs { + h2.appendAttr(wa, a) + } + + h2.attrs = wa + return h2 } diff --git a/handler_test.go b/handler_test.go index 2fdd763..bb57051 100644 --- a/handler_test.go +++ b/handler_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/fatih/color" "github.com/tlinden/yadu" ) @@ -28,10 +29,12 @@ type Enemy struct { } type Tests struct { - name string - want string - negate bool - opts *yadu.Options + name string + want string + negate bool + opts yadu.Options + with slog.Attr + haswith bool } const testTimeFormat = "03:04.05" @@ -65,7 +68,7 @@ var tests = []Tests{ { name: "has-no-time", want: time.Now().Format(yadu.DefaultTimeFormat), - opts: &yadu.Options{ + opts: yadu.Options{ ReplaceAttr: removeTime, }, negate: true, @@ -73,12 +76,60 @@ var tests = []Tests{ { name: "has-custom-time", want: time.Now().Format(testTimeFormat), - opts: &yadu.Options{ + opts: yadu.Options{ TimeFormat: testTimeFormat, }, negate: false, }, - // FIXME: add WithGroup + WithAttr tests + { + name: "with-group", + want: "pid:", + negate: false, + with: slog.Group("program_info", + slog.Int("pid", 1923), + slog.Bool("alive", true), + ), + haswith: true, + }, + { + name: "has-debug", + want: "DEBUG", + negate: false, + opts: yadu.Options{ + Level: slog.LevelDebug, + }, + }, + { + name: "has-warn", + want: "WARN", + negate: false, + opts: yadu.Options{ + Level: slog.LevelWarn, + }, + }, + { + name: "has-error", + want: "ERROR", + negate: false, + opts: yadu.Options{ + Level: slog.LevelError, + }, + }, + { + // check if output is NOT colored when disabling it + name: "disable-color", + want: "\x1b[0m", + negate: true, + opts: yadu.Options{ + NoColor: true, + }, + }, + { + // check if output is colored + name: "enable-color", + want: "\x1b[0m", + negate: false, + }, } func GetEnemy() *Enemy { @@ -101,9 +152,29 @@ func Test(t *testing.T) { for _, tt := range tests { var buf bytes.Buffer - logger := slog.New(yadu.NewHandler(&buf, tt.opts)) + logger := slog.New(yadu.NewHandler(&buf, &tt.opts)) + + if !tt.with.Equal(slog.Attr{}) { + logger = logger.With(tt.with) + } + + if !tt.opts.NoColor { + color.NoColor = false + } + + slog.SetDefault(logger) + + switch tt.opts.Level { + case slog.LevelDebug: + logger.Debug("attack", "enemy", GetEnemy()) + case slog.LevelWarn: + logger.Warn("attack", "enemy", GetEnemy()) + case slog.LevelError: + logger.Error("attack", "enemy", GetEnemy()) + default: + logger.Info("attack", "enemy", GetEnemy()) + } - logger.Info("attack", "enemy", GetEnemy()) got := buf.String() if strings.Contains(got, tt.want) == tt.negate {