diff --git a/buffer.go b/buffer.go index 988f1c6..4d7321a 100644 --- a/buffer.go +++ b/buffer.go @@ -7,7 +7,7 @@ type buffer []byte var bufPool = sync.Pool{ New: func() any { b := make(buffer, 0, 1024) - return &b + return (*buffer)(&b) }, } diff --git a/handler.go b/handler.go index de05d9c..cfd1de6 100644 --- a/handler.go +++ b/handler.go @@ -38,8 +38,8 @@ const ( const keyErr = "err" var ( - defaultTimeFormat = time.StampMilli defaultLevel = slog.LevelInfo + defaultTimeFormat = time.StampMilli ) // Options for a slog.Handler that writes tinted logs. A zero Options consists @@ -64,32 +64,30 @@ type Options struct { NoColor bool } -// NewHandler creates a [slog.Handler] that writes tinted logs to Writer w with -// the given options. -func (opts Options) NewHandler(w io.Writer) slog.Handler { +// NewHandler creates a [slog.Handler] that writes tinted logs to Writer w, +// using the default options. If opts is nil, the default options are used. +func NewHandler(w io.Writer, opts *Options) slog.Handler { h := &handler{ - w: w, - addSource: opts.AddSource, - level: defaultLevel, - replaceAttr: opts.ReplaceAttr, - timeFormat: defaultTimeFormat, - noColor: opts.NoColor, + w: w, + level: defaultLevel, + timeFormat: defaultTimeFormat, } + if opts == nil { + return h + } + + h.addSource = opts.AddSource if opts.Level != nil { h.level = opts.Level.Level() } + h.replaceAttr = opts.ReplaceAttr if opts.TimeFormat != "" { h.timeFormat = opts.TimeFormat } + h.noColor = opts.NoColor return h } -// NewHandler creates a [slog.Handler] that writes tinted logs to Writer w, -// using the default options. -func NewHandler(w io.Writer) slog.Handler { - return Options{}.NewHandler(w) -} - // handler implements a [slog.Handler]. type handler struct { attrs string @@ -165,10 +163,16 @@ func (h *handler) Handle(_ context.Context, r slog.Record) error { fs := runtime.CallersFrames([]uintptr{r.PC}) f, _ := fs.Next() if f.File != "" { + src := &slog.Source{ + Function: f.Function, + File: f.File, + Line: f.Line, + } + if rep == nil { - h.appendSource(buf, f) + h.appendSource(buf, src) buf.WriteByte(' ') - } else if a := rep(h.groupsSlice, slog.Any(slog.SourceKey, f)); a.Key != "" { + } else if a := rep(h.groupsSlice, slog.Any(slog.SourceKey, src)); a.Key != "" { appendValue(buf, a.Value, false) buf.WriteByte(' ') } @@ -277,13 +281,13 @@ func (h *handler) appendLevel(buf *buffer, level slog.Level) { } } -func (h *handler) appendSource(buf *buffer, f runtime.Frame) { - dir, file := filepath.Split(f.File) +func (h *handler) appendSource(buf *buffer, src *slog.Source) { + dir, file := filepath.Split(src.File) buf.WriteStringIf(!h.noColor, ansiFaint) buf.WriteString(filepath.Join(filepath.Base(dir), file)) buf.WriteByte(':') - buf.WriteString(strconv.Itoa(f.Line)) + buf.WriteString(strconv.Itoa(src.Line)) buf.WriteStringIf(!h.noColor, ansiReset) } diff --git a/handler_test.go b/handler_test.go index d16cc83..cf3008d 100644 --- a/handler_test.go +++ b/handler_test.go @@ -18,10 +18,10 @@ import ( var faketime = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) func Example() { - slog.SetDefault(slog.New(tint.Options{ + slog.SetDefault(slog.New(tint.NewHandler(os.Stderr, &tint.Options{ Level: slog.LevelDebug, TimeFormat: time.Kitchen, - }.NewHandler(os.Stderr))) + }))) slog.Info("Starting server", "addr", ":8080", "env", "production") slog.Debug("Connected to DB", "db", "myapp", "host", "localhost:5432") @@ -39,7 +39,7 @@ func TestHandler(t *testing.T) { } tests := []struct { - Opts tint.Options + Opts *tint.Options F func(l *slog.Logger) Want string }{ @@ -92,7 +92,7 @@ func TestHandler(t *testing.T) { Want: `Nov 10 23:00:00.000 INF test slice="[a b c]" map="map[a:1 b:2 c:3]"`, }, { - Opts: tint.Options{ + Opts: &tint.Options{ AddSource: true, }, F: func(l *slog.Logger) { @@ -101,7 +101,7 @@ func TestHandler(t *testing.T) { Want: `Nov 10 23:00:00.000 INF tint/handler_test.go:99 test key=val`, }, { - Opts: tint.Options{ + Opts: &tint.Options{ TimeFormat: time.Kitchen, }, F: func(l *slog.Logger) { @@ -110,7 +110,7 @@ func TestHandler(t *testing.T) { Want: `11:00PM INF test key=val`, }, { - Opts: tint.Options{ + Opts: &tint.Options{ ReplaceAttr: drop(slog.TimeKey), }, F: func(l *slog.Logger) { @@ -119,7 +119,7 @@ func TestHandler(t *testing.T) { Want: `INF test key=val`, }, { - Opts: tint.Options{ + Opts: &tint.Options{ ReplaceAttr: drop(slog.LevelKey), }, F: func(l *slog.Logger) { @@ -128,7 +128,7 @@ func TestHandler(t *testing.T) { Want: `Nov 10 23:00:00.000 test key=val`, }, { - Opts: tint.Options{ + Opts: &tint.Options{ ReplaceAttr: drop(slog.MessageKey), }, F: func(l *slog.Logger) { @@ -137,7 +137,7 @@ func TestHandler(t *testing.T) { Want: `Nov 10 23:00:00.000 INF key=val`, }, { - Opts: tint.Options{ + Opts: &tint.Options{ ReplaceAttr: drop(slog.TimeKey, slog.LevelKey, slog.MessageKey), }, F: func(l *slog.Logger) { @@ -146,7 +146,7 @@ func TestHandler(t *testing.T) { Want: `key=val`, }, { - Opts: tint.Options{ + Opts: &tint.Options{ ReplaceAttr: drop("key"), }, F: func(l *slog.Logger) { @@ -155,7 +155,7 @@ func TestHandler(t *testing.T) { Want: `Nov 10 23:00:00.000 INF test`, }, { - Opts: tint.Options{ + Opts: &tint.Options{ ReplaceAttr: drop("key"), }, F: func(l *slog.Logger) { @@ -164,7 +164,7 @@ func TestHandler(t *testing.T) { Want: `Nov 10 23:00:00.000 INF test group.key=val group.key2=val2`, }, { - Opts: tint.Options{ + Opts: &tint.Options{ ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { if a.Key == "key" && len(groups) == 1 && groups[0] == "group" { return slog.Attr{} @@ -178,7 +178,7 @@ func TestHandler(t *testing.T) { Want: `Nov 10 23:00:00.000 INF test group.key2=val2`, }, { - Opts: tint.Options{ + Opts: &tint.Options{ ReplaceAttr: replace(slog.IntValue(42), slog.TimeKey), }, F: func(l *slog.Logger) { @@ -187,7 +187,7 @@ func TestHandler(t *testing.T) { Want: `42 INF test key=val`, }, { - Opts: tint.Options{ + Opts: &tint.Options{ ReplaceAttr: replace(slog.StringValue("INFO"), slog.LevelKey), }, F: func(l *slog.Logger) { @@ -196,7 +196,7 @@ func TestHandler(t *testing.T) { Want: `Nov 10 23:00:00.000 INFO test key=val`, }, { - Opts: tint.Options{ + Opts: &tint.Options{ ReplaceAttr: replace(slog.IntValue(42), slog.MessageKey), }, F: func(l *slog.Logger) { @@ -205,7 +205,7 @@ func TestHandler(t *testing.T) { Want: `Nov 10 23:00:00.000 INF 42 key=val`, }, { - Opts: tint.Options{ + Opts: &tint.Options{ ReplaceAttr: replace(slog.IntValue(42), "key"), }, F: func(l *slog.Logger) { @@ -214,7 +214,7 @@ func TestHandler(t *testing.T) { Want: `Nov 10 23:00:00.000 INF test key=42 key2=val2`, }, { - Opts: tint.Options{ + Opts: &tint.Options{ ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { return slog.Attr{} }, @@ -266,8 +266,11 @@ func TestHandler(t *testing.T) { for i, test := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { var buf bytes.Buffer + if test.Opts == nil { + test.Opts = &tint.Options{} + } test.Opts.NoColor = true - l := slog.New(test.Opts.NewHandler(&buf)) + l := slog.New(tint.NewHandler(&buf, test.Opts)) test.F(l) got := strings.TrimRight(buf.String(), "\n") @@ -319,7 +322,7 @@ func BenchmarkLogAttrs(b *testing.B) { Name string H slog.Handler }{ - {"tint", tint.NewHandler(io.Discard)}, + {"tint", tint.NewHandler(io.Discard, nil)}, {"text", slog.NewTextHandler(io.Discard, nil)}, {"json", slog.NewJSONHandler(io.Discard, nil)}, {"discard", new(discarder)},