From 33d690663d053e17c14fd4c766f9b4dcdcb5574b Mon Sep 17 00:00:00 2001 From: lmittmann <3458786+lmittmann@users.noreply.github.com> Date: Sat, 15 Apr 2023 18:00:08 +0200 Subject: [PATCH] Added `Options.AddSource` (#14) * added `Options.AddSource` * improved doc & minor refactoring --------- Co-authored-by: lmittmann --- README.md | 2 ++ handler.go | 76 ++++++++++++++++++++++++++++++++++++++----------- handler_test.go | 9 ++++++ 3 files changed, 71 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 051d6b4..c048ff8 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ Package `tint` provides a [`slog.Handler`](https://pkg.go.dev/golang.org/x/exp/slog#Handler) that writes tinted logs. The output format is inspired by the `zerolog.ConsoleWriter`. +The output format can be customized using [`Options`](https://pkg.go.dev/github.com/lmittmann/tint#Options) which is a drop-in replacement for [`slog.HandlerOptions`](https://pkg.go.dev/golang.org/x/exp/slog#HandlerOptions). + ``` go get github.com/lmittmann/tint ``` diff --git a/handler.go b/handler.go index c834ff6..af8eeb6 100644 --- a/handler.go +++ b/handler.go @@ -2,6 +2,9 @@ Package tint provides a [slog.Handler] that writes tinted logs. The output format is inspired by the [zerolog.ConsoleWriter]. +The output format can be customized using [Options], which is a drop-in +replacement for [slog.HandlerOptions]. + [zerolog.ConsoleWriter]: https://pkg.go.dev/github.com/rs/zerolog#ConsoleWriter */ package tint @@ -11,6 +14,8 @@ import ( "encoding" "fmt" "io" + "path/filepath" + "runtime" "strconv" "sync" "time" @@ -32,23 +37,31 @@ const ( const keyErr = "err" -var defaultTimeFormat = time.StampMilli +var ( + defaultTimeFormat = time.StampMilli + defaultLevel = slog.LevelInfo +) // Options for a slog.Handler that writes tinted logs. A zero Options consists // entirely of default values. +// +// Options can be used as a drop-in replacement for [slog.HandlerOptions]. type Options struct { + // Enable source code location (Default: false) + AddSource bool + // Minimum level to log (Default: slog.LevelInfo) - Level slog.Level + Level slog.Leveler + + // ReplaceAttr is called to rewrite each non-group attribute before it is logged. + // See https://pkg.go.dev/golang.org/x/exp/slog#HandlerOptions for details. + ReplaceAttr func(groups []string, attr slog.Attr) slog.Attr // Time format (Default: time.StampMilli) TimeFormat string // Disable color (Default: false) NoColor bool - - // ReplaceAttr is called to rewrite each non-group attribute before it is logged. - // See https://pkg.go.dev/golang.org/x/exp/slog#HandlerOptions for details. - ReplaceAttr func(groups []string, attr slog.Attr) slog.Attr } // NewHandler creates a [slog.Handler] that writes tinted logs to Writer w with @@ -56,13 +69,17 @@ type Options struct { func (opts Options) NewHandler(w io.Writer) slog.Handler { h := &handler{ w: w, - level: opts.Level, - timeFormat: opts.TimeFormat, - noColor: opts.NoColor, + addSource: opts.AddSource, + level: defaultLevel, replaceAttr: opts.ReplaceAttr, + timeFormat: defaultTimeFormat, + noColor: opts.NoColor, } - if h.timeFormat == "" { - h.timeFormat = defaultTimeFormat + if opts.Level != nil { + h.level = opts.Level.Level() + } + if opts.TimeFormat != "" { + h.timeFormat = opts.TimeFormat } return h } @@ -80,12 +97,13 @@ type handler struct { groupsSlice []string mu sync.Mutex - w io.Writer // Output writer + w io.Writer - level slog.Level // Minimum level to log (Default: slog.LevelInfo) - timeFormat string // Time format (Default: time.StampMilli) - noColor bool // Disable color (Default: false) + addSource bool + level slog.Level replaceAttr func([]string, slog.Attr) slog.Attr + timeFormat string + noColor bool } func (h *handler) clone() *handler { @@ -94,10 +112,11 @@ func (h *handler) clone() *handler { groups: h.groups, groupsSlice: h.groupsSlice, w: h.w, + addSource: h.addSource, level: h.level, + replaceAttr: h.replaceAttr, timeFormat: h.timeFormat, noColor: h.noColor, - replaceAttr: h.replaceAttr, } } @@ -141,6 +160,21 @@ func (h *handler) Handle(_ context.Context, r slog.Record) error { buf.WriteByte(' ') } + // write source + if h.addSource { + fs := runtime.CallersFrames([]uintptr{r.PC}) + f, _ := fs.Next() + if f.File != "" { + if rep == nil { + h.appendSource(buf, f) + buf.WriteByte(' ') + } else if a := rep(h.groupsSlice, slog.Any(slog.SourceKey, f)); a.Key != "" { + appendValue(buf, a.Value, false) + buf.WriteByte(' ') + } + } + } + // write message if rep == nil { buf.WriteString(r.Message) @@ -242,6 +276,16 @@ func (h *handler) appendLevel(buf *buffer, level slog.Level) { } } +func (h *handler) appendSource(buf *buffer, f runtime.Frame) { + dir, file := filepath.Split(f.File) + + buf.WriteStringIf(!h.noColor, ansiFaint) + buf.WriteString(filepath.Join(filepath.Base(dir), file)) + buf.WriteByte(':') + buf.WriteString(strconv.Itoa(f.Line)) + buf.WriteStringIf(!h.noColor, ansiReset) +} + func (h *handler) appendAttr(buf *buffer, attr slog.Attr, groups string) { if attr.Key == "" { return diff --git a/handler_test.go b/handler_test.go index 2bba2b9..75f9332 100644 --- a/handler_test.go +++ b/handler_test.go @@ -91,6 +91,15 @@ func TestHandler(t *testing.T) { }, Want: `Nov 11 00:00:00.000 INF test slice="[a b c]" map="map[a:1 b:2 c:3]"`, }, + { + Opts: tint.Options{ + AddSource: true, + }, + F: func(l *slog.Logger) { + l.Info("test", "key", "val") + }, + Want: `Nov 11 00:00:00.000 INF tint/handler_test.go:99 test key=val`, + }, { Opts: tint.Options{ TimeFormat: time.Kitchen,