From db2990547c5b2378d2c33eabed81c231e503b8f7 Mon Sep 17 00:00:00 2001 From: Janelle Law Date: Fri, 13 Oct 2023 15:17:22 +0000 Subject: [PATCH] add totem formatting options --- go.mod | 2 +- go.sum | 8 ++-- pkg/logger/color_handler.go | 8 +++- pkg/logger/logger.go | 93 ++++++++++++++++++++++-------------- pkg/logger/totem_name.go | 95 +++++++++++++++++++++++++++++++++++++ 5 files changed, 165 insertions(+), 41 deletions(-) create mode 100644 pkg/logger/totem_name.go diff --git a/go.mod b/go.mod index 45a95ab0e2..d1eec522b2 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( github.com/kralicky/gpkg v0.0.0-20220311205216-0d8ea9557555 github.com/kralicky/kmatch v0.0.0-20230301203314-20f658a0e56c github.com/kralicky/ragu v1.0.11-0.20230627162951-2dd00e0cbbf3 - github.com/kralicky/totem v1.2.1 + github.com/kralicky/totem v1.2.2-0.20231013192319-ab56f5483c40 github.com/kralicky/yaml/v3 v3.0.0-20220520012407-b0e7050bd81d github.com/lestrrat-go/backoff/v2 v2.0.8 github.com/lestrrat-go/jwx v1.2.26 diff --git a/go.sum b/go.sum index a60150d9de..6ef4acd763 100644 --- a/go.sum +++ b/go.sum @@ -1537,6 +1537,8 @@ github.com/ionos-cloud/sdk-go/v6 v6.0.4 h1:4LoWeM7WtcDqYDjlntqQ3fD6XaENlCw2YqiVW github.com/ionos-cloud/sdk-go/v6 v6.0.4/go.mod h1:UE3V/2DjnqD5doOqtjYqzJRMpI1RiwrvuuSEPX1pdnk= github.com/jan-law/opentelemetry-collector-contrib/pkg/stanza v0.0.0-20231011183323-1da8f0d6468b h1:FS3nr4khTxuxKEgmbfbsK6s9+ZJ6dvrPr7C/3sSprfY= github.com/jan-law/opentelemetry-collector-contrib/pkg/stanza v0.0.0-20231011183323-1da8f0d6468b/go.mod h1:N2K2atD+Oc1jFamDAe/H1EhtWQ+x8GBxsgHYn0JPN0I= +github.com/jan-law/slog-sampling v0.0.0-20231013170129-ef692b2d04c4 h1:05hLE3ki21Fnkh0vxNBjW1hgv4aKK222Cu4tutU1rNY= +github.com/jan-law/slog-sampling v0.0.0-20231013170129-ef692b2d04c4/go.mod h1:ZcJuHhGp3oTkGnOyl+ePWSci8XzCWQENC6JxkrE75EM= github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -1652,8 +1654,8 @@ github.com/kralicky/kmatch v0.0.0-20230301203314-20f658a0e56c h1:1kKbyl5GtGk/D+6 github.com/kralicky/kmatch v0.0.0-20230301203314-20f658a0e56c/go.mod h1:GXnSgs1TGXa8aX1hO9SUE1o3pLfggl5hkBMXdVWD8dc= github.com/kralicky/ragu v1.0.11-0.20230627162951-2dd00e0cbbf3 h1:UB8Fk6c8Ait8DLtemgT/ItRdUpxJQHMDntqFopFt6/Q= github.com/kralicky/ragu v1.0.11-0.20230627162951-2dd00e0cbbf3/go.mod h1:njQGnwU5IHm4Qx6/oOY0ll7Z6WA0apfP0ClpVA0M27Q= -github.com/kralicky/totem v1.2.1 h1:ayd+IHbmmWhgFXJfzTHg0mJ2mr/IYHCh/nDqYyd6yCk= -github.com/kralicky/totem v1.2.1/go.mod h1:DFlQM6o5n1AWrU/+wd3hjLKoKZ+a0TPRUQxnCQvGKyY= +github.com/kralicky/totem v1.2.2-0.20231013192319-ab56f5483c40 h1:Q7acvQcPTlse5kIOziWMmwcCk2GjVMe9lCOPOO/bjEM= +github.com/kralicky/totem v1.2.2-0.20231013192319-ab56f5483c40/go.mod h1:EmT24IweS2gIdMcQOZuhppvfs3cVs22pIos9RzFTHsQ= github.com/kralicky/yaml/v3 v3.0.0-20220520012407-b0e7050bd81d h1:kLfaaFdmCHKZCvL4DzQ7T9YsAVSBqZez34zaegldkls= github.com/kralicky/yaml/v3 v3.0.0-20220520012407-b0e7050bd81d/go.mod h1:z4cKsjE0B5RlhFSbWcZNh70UEjtwrj8fpG1QXyU2KuI= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -2171,8 +2173,6 @@ github.com/samber/lo v1.36.1-0.20230320154156-56ef8fe8a306 h1:9kOF1vK0pvroiAnPkM github.com/samber/lo v1.36.1-0.20230320154156-56ef8fe8a306/go.mod h1:kV0TUY2yeRZLmppP/VYD1MhUfBK78z2xFcmv/X2uyvE= github.com/samber/slog-multi v1.0.1 h1:Owf7RnxBZokPuGuuecKH8bEX1iRepjJJgmXd5BTxvl8= github.com/samber/slog-multi v1.0.1/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo= -github.com/samber/slog-sampling v1.0.0 h1:k+pPKId9RXYwHE8MK3m0sP1xTI5I/swpSi89dBjzBoM= -github.com/samber/slog-sampling v1.0.0/go.mod h1:S138KhbzB59zvOLeAAf6MesTpOwAsoXASPb50Zdto2g= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE= github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= diff --git a/pkg/logger/color_handler.go b/pkg/logger/color_handler.go index 0d5cd68150..d804dd1b9c 100644 --- a/pkg/logger/color_handler.go +++ b/pkg/logger/color_handler.go @@ -43,6 +43,7 @@ type colorHandler struct { attrsPrefix string // attrs started from With groups []string // all groups started from WithGroup groupPrefix string // groups started from Group + appendName bool mu sync.Mutex w io.Writer } @@ -53,6 +54,8 @@ func newColorHandler(w io.Writer, opts *LoggerOptions) slog.Handler { Level: DefaultLogLevel, AddSource: true, ColorEnabled: ColorEnabled(), + AppendName: true, + TimeFormat: DefaultTimeFormat, } } @@ -66,6 +69,7 @@ func newColorHandler(w io.Writer, opts *LoggerOptions) slog.Handler { replaceAttr: opts.ReplaceAttr, colorEnabled: opts.ColorEnabled, timeFormat: opts.TimeFormat, + appendName: opts.AppendName, w: w, } } @@ -133,7 +137,9 @@ func (h *colorHandler) Handle(_ context.Context, r slog.Record) error { buf.WriteByte(' ') } - h.writeGroups(buf, h.groups) + if h.appendName { + h.writeGroups(buf, h.groups) + } if h.addSource { h.writeSource(buf, r.PC) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 7c044bece0..2148727721 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -41,13 +41,15 @@ func AsciiLogo() string { } type LoggerOptions struct { - Level slog.Level - AddSource bool - ReplaceAttr func(groups []string, a slog.Attr) slog.Attr - Writer io.Writer - ColorEnabled bool - Sampling *slogsampling.ThresholdSamplingOption - TimeFormat string + Level slog.Level + AddSource bool + ReplaceAttr func(groups []string, a slog.Attr) slog.Attr + Writer io.Writer + ColorEnabled bool + Sampling *slogsampling.ThresholdSamplingOption + TimeFormat string + TotemFormatEnabled bool + AppendName bool } func ParseLevel(lvl string) slog.Level { @@ -104,12 +106,34 @@ func WithTimeFormat(format string) LoggerOption { func WithSampling(cfg *slogsampling.ThresholdSamplingOption) LoggerOption { return func(o *LoggerOptions) { o.Sampling = &slogsampling.ThresholdSamplingOption{ - Tick: cfg.Tick, - Threshold: cfg.Threshold, - Rate: cfg.Rate, - OnDropped: logSampler.onDroppedHook, - OnAccepted: logSampler.onAcceptedHook, + Tick: cfg.Tick, + Threshold: cfg.Threshold, + Rate: cfg.Rate, + OnDropped: logSampler.onDroppedHook, } + o.ReplaceAttr = func(groups []string, a slog.Attr) slog.Attr { + if a.Key == slog.MessageKey { + msg := a.Value.String() + count, _ := logSampler.dropped.Load(msg) + if count > 0 { + numDropped, _ := logSampler.dropped.LoadAndDelete(msg) + a.Value = slog.StringValue(fmt.Sprintf("x%d %s", numDropped+1, msg)) + } + } + return a + } + } +} + +func WithTotemFormat(enable bool) LoggerOption { + return func(o *LoggerOptions) { + o.TotemFormatEnabled = enable + } +} + +func WithAppendName(enable bool) LoggerOption { + return func(o *LoggerOptions) { + o.AppendName = enable } } @@ -120,6 +144,7 @@ func colorHandlerWithOptions(opts ...LoggerOption) slog.Handler { Level: DefaultLogLevel, AddSource: DefaultAddSource, TimeFormat: DefaultTimeFormat, + AppendName: true, } options.apply(opts...) @@ -128,13 +153,30 @@ func colorHandlerWithOptions(opts ...LoggerOption) slog.Handler { DefaultWriter = os.Stdout } + var middlewares []slogmulti.Middleware + if options.TotemFormatEnabled { + options.AppendName = false + options.Writer = os.Stderr + middlewares = append(middlewares, newTotemNameMiddleware()) + } + if options.Sampling != nil { + middlewares = append(middlewares, options.Sampling.NewMiddleware()) + } + var chain *slogmulti.PipeBuilder + for i, middleware := range middlewares { + if i == 0 { + chain = slogmulti.Pipe(middleware) + } else { + chain = chain.Pipe(middleware) + } + } + handler := newColorHandler(options.Writer, options) - if options.Sampling != nil { - return slogmulti. - Pipe(options.Sampling.NewMiddleware()). - Handler(handler) + if chain != nil { + handler = chain.Handler(handler) } + return handler } @@ -163,22 +205,3 @@ func (s *sampler) onDroppedHook(_ context.Context, r slog.Record) { count, _ := s.dropped.LoadOrStore(key, 0) s.dropped.Store(key, count+1) } - -func (s *sampler) onAcceptedHook(_ context.Context, r slog.Record) { - attrs := []slog.Attr{} - - msg := r.Message - count, _ := s.dropped.Load(msg) - if count > 0 { - numDropped, _ := s.dropped.LoadAndDelete(msg) - msg = fmt.Sprintf("x%d %s", numDropped+1, msg) - } - - r.Attrs(func(attr slog.Attr) bool { - attrs = append(attrs, attr) - return true - }) - - r = slog.NewRecord(r.Time, r.Level, msg, r.PC) - r.AddAttrs(attrs...) -} diff --git a/pkg/logger/totem_name.go b/pkg/logger/totem_name.go new file mode 100644 index 0000000000..715548455b --- /dev/null +++ b/pkg/logger/totem_name.go @@ -0,0 +1,95 @@ +package logger + +import ( + "context" + "fmt" + "log/slog" + "math" + "math/rand" + "strings" + + "github.com/charmbracelet/lipgloss" + gsync "github.com/kralicky/gpkg/sync" + slogmulti "github.com/samber/slog-multi" +) + +var ( + colorCache gsync.Map[string, lipgloss.Style] +) + +func init() { + colorCache.Store("totem", lipgloss.NewStyle().Background(lipgloss.Color("15")).Foreground(lipgloss.Color("0"))) + +} + +func newRandomForegroundStyle() lipgloss.Style { + r := math.Round(rand.Float64()*127) + 127 + g := math.Round(rand.Float64()*127) + 127 + b := math.Round(rand.Float64()*127) + 127 + return lipgloss.NewStyle().Foreground(lipgloss.Color(fmt.Sprintf("#%02x%02x%02x", int(r), int(g), int(b)))) +} + +func newTotemNameMiddleware() slogmulti.Middleware { + return func(next slog.Handler) slog.Handler { + return &totemNameMiddleware{ + next: next, + } + } +} + +type totemNameMiddleware struct { + next slog.Handler + groups []string +} + +func (h *totemNameMiddleware) Enabled(ctx context.Context, level slog.Level) bool { + return h.next.Enabled(ctx, level) +} + +func (h *totemNameMiddleware) Handle(ctx context.Context, record slog.Record) error { + attrs := []slog.Attr{} + + record.Attrs(func(attr slog.Attr) bool { + attrs = append(attrs, attr) + return true + }) + + sb := strings.Builder{} + for i, part := range h.groups { + if c, ok := colorCache.Load(part); !ok { + newStyle := newRandomForegroundStyle() + colorCache.Store(part, newStyle) + sb.WriteString(newStyle.Render(part)) + } else { + sb.WriteString(c.Render(part)) + } + if i < len(h.groups)-1 { + sb.WriteString(".") + } else { + sb.WriteByte(' ') + } + } + + // insert logger name before message + sb.WriteString(record.Message) + + record = slog.NewRecord(record.Time, record.Level, sb.String(), record.PC) + record.AddAttrs(attrs...) + + return h.next.Handle(ctx, record) +} + +func (h *totemNameMiddleware) WithAttrs(attrs []slog.Attr) slog.Handler { + + return &totemNameMiddleware{ + next: h.next.WithAttrs(attrs), + groups: h.groups, + } +} + +func (h *totemNameMiddleware) WithGroup(name string) slog.Handler { + return &totemNameMiddleware{ + next: h.next.WithGroup(name), + groups: append(h.groups, name), + } +}