From 3a2a41687bec822dd140e61fd0ab7be13045ee72 Mon Sep 17 00:00:00 2001 From: Russ Egan Date: Tue, 7 Jan 2025 15:24:01 -0600 Subject: [PATCH 1/2] Use fmt for errors, like slog.TextHandler Uses fmt.Fprintf with "%+v" to print errors. Useful with some error packages which render additional information like stacktraces. Addresses #7 --- buffer.go | 5 +++++ encoding.go | 4 +++- handler_test.go | 15 ++++++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/buffer.go b/buffer.go index ddff9c3..f05182a 100644 --- a/buffer.go +++ b/buffer.go @@ -45,6 +45,11 @@ func (b *buffer) WriteTo(dst io.Writer) (int64, error) { return int64(n), nil } +func (b *buffer) Write(bt []byte) (int, error) { + *b = append(*b, bt...) + return len(bt), nil +} + func (b *buffer) Reset() { *b = (*b)[:0] } diff --git a/encoding.go b/encoding.go index 6088e65..6214a69 100644 --- a/encoding.go +++ b/encoding.go @@ -144,7 +144,9 @@ func (e encoder) writeValue(buf *buffer, value slog.Value) { case slog.KindAny: switch v := value.Any().(type) { case error: - e.writeColoredString(buf, v.Error(), e.opts.Theme.AttrValueError()) + e.withColor(buf, e.opts.Theme.AttrValueError(), func() { + fmt.Fprintf(buf, "%+v", v) + }) return case fmt.Stringer: e.writeColoredString(buf, v.String(), attrValue) diff --git a/handler_test.go b/handler_test.go index eb09629..a60cb42 100644 --- a/handler_test.go +++ b/handler_test.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "io" "log/slog" "os" "path/filepath" @@ -72,6 +73,17 @@ func (v *theValuer) LogValue() slog.Value { return slog.StringValue(fmt.Sprintf("The word is '%s'", v.word)) } +type formatterError struct { + error +} + +func (e *formatterError) Format(f fmt.State, verb rune) { + if verb == 'v' && f.Flag('+') { + io.WriteString(f, "formatted ") + } + io.WriteString(f, e.Error()) +} + func TestHandler_Attr(t *testing.T) { buf := bytes.Buffer{} h := NewHandler(&buf, &HandlerOptions{NoColor: true}) @@ -87,6 +99,7 @@ func TestHandler_Attr(t *testing.T) { slog.Duration("dur", time.Second), slog.Group("group", slog.String("foo", "bar"), slog.Group("subgroup", slog.String("foo", "bar"))), slog.Any("err", errors.New("the error")), + slog.Any("formattedError", &formatterError{errors.New("the error")}), slog.Any("stringer", theStringer{}), slog.Any("nostringer", noStringer{Foo: "bar"}), // Resolve LogValuer items in addition to Stringer items. @@ -102,7 +115,7 @@ func TestHandler_Attr(t *testing.T) { ) AssertNoError(t, h.Handle(context.Background(), rec)) - expected := fmt.Sprintf("%s INF foobar bool=true int=-12 uint=12 float=3.14 foo=bar time=%s dur=1s group.foo=bar group.subgroup.foo=bar err=the error stringer=stringer nostringer={bar} valuer=The word is 'distant'\n", now.Format(time.DateTime), now.Format(time.DateTime)) + expected := fmt.Sprintf("%s INF foobar bool=true int=-12 uint=12 float=3.14 foo=bar time=%s dur=1s group.foo=bar group.subgroup.foo=bar err=the error formattedError=formatted the error stringer=stringer nostringer={bar} valuer=The word is 'distant'\n", now.Format(time.DateTime), now.Format(time.DateTime)) AssertEqual(t, expected, buf.String()) } From 7cda51fa594d114cc2eb8049e38802742b916070 Mon Sep 17 00:00:00 2001 From: Russ Egan Date: Fri, 10 Jan 2025 11:41:45 -0600 Subject: [PATCH 2/2] optimize for errors which don't implement fmt.Formatter If the error doesn't implement fmt.Formatter, as most won't, its faster to use err.Error() than always use fmt to print the error. --- encoding.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/encoding.go b/encoding.go index 6214a69..4e39eea 100644 --- a/encoding.go +++ b/encoding.go @@ -144,9 +144,13 @@ func (e encoder) writeValue(buf *buffer, value slog.Value) { case slog.KindAny: switch v := value.Any().(type) { case error: - e.withColor(buf, e.opts.Theme.AttrValueError(), func() { - fmt.Fprintf(buf, "%+v", v) - }) + if _, ok := v.(fmt.Formatter); ok { + e.withColor(buf, e.opts.Theme.AttrValueError(), func() { + fmt.Fprintf(buf, "%+v", v) + }) + } else { + e.writeColoredString(buf, v.Error(), e.opts.Theme.AttrValueError()) + } return case fmt.Stringer: e.writeColoredString(buf, v.String(), attrValue)