Skip to content

Commit d586353

Browse files
authored
Merge pull request #8 from jsteenb2/chore/err_formatters
feat: update frame/stackframes formatter and add e and joinE formatters
2 parents 4be1dc0 + 92a757c commit d586353

File tree

6 files changed

+119
-65
lines changed

6 files changed

+119
-65
lines changed

err_impl.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package errors
22

33
import (
44
"cmp"
5+
"fmt"
6+
"io"
57
)
68

79
func newE(opts ...any) error {
@@ -71,6 +73,18 @@ func (err *e) Error() string {
7173
return msg
7274
}
7375

76+
func (err *e) Format(s fmt.State, verb rune) {
77+
switch verb {
78+
case 'v':
79+
fallthrough
80+
case 's':
81+
io.WriteString(s, err.Error()+" ")
82+
err.stackTrace().Format(s, fmtInline)
83+
case 'q':
84+
fmt.Fprintf(s, "%q", err.Error())
85+
}
86+
}
87+
7488
// Fields represents the meaningful logging fields from the error. These include
7589
// all the KVs, error kind, and stack trace for the error and all wrapped error
7690
// fields. This is an incredibly powerful tool to enhance the logging/observability

errors_stack_traces_test.go

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,8 @@ func Test_Errors(t *testing.T) {
195195
),
196196
want: wants{
197197
msg: `2 errors occurred:
198-
* first error
199-
* second error
198+
* first error [ github.com/jsteenb2/errors/errors_stack_traces_test.go:189[Test_Errors], github.com/jsteenb2/errors/errors_stack_traces_test.go:190[Test_Errors] ]
199+
* second error [ github.com/jsteenb2/errors/errors_stack_traces_test.go:192[Test_Errors] ]
200200
`,
201201
fields: []any{
202202
"multi_err", []any{
@@ -227,3 +227,39 @@ func Test_Errors(t *testing.T) {
227227
})
228228
}
229229
}
230+
231+
func TestE_Format(t *testing.T) {
232+
t.Run("e error", func(t *testing.T) {
233+
err := errors.Wrap(
234+
errors.New("inner msg"),
235+
"outter",
236+
)
237+
238+
want := `outter: inner msg [ github.com/jsteenb2/errors/errors_stack_traces_test.go:233[TestE_Format.func1], github.com/jsteenb2/errors/errors_stack_traces_test.go:234[TestE_Format.func1] ]`
239+
eq(t, want, fmt.Sprintf("%v", err))
240+
eq(t, want, fmt.Sprintf("%s", err))
241+
242+
want = `"outter: inner msg"`
243+
eq(t, want, fmt.Sprintf("%q", err))
244+
})
245+
246+
t.Run("joinE error", func(t *testing.T) {
247+
err := errors.Join(
248+
errors.New("simple"),
249+
errors.Wrap(
250+
errors.New("deep"),
251+
"outter msg",
252+
),
253+
)
254+
255+
want := `2 errors occurred:
256+
* simple [ github.com/jsteenb2/errors/errors_stack_traces_test.go:248[TestE_Format.func2] ]
257+
* outter msg: deep [ github.com/jsteenb2/errors/errors_stack_traces_test.go:249[TestE_Format.func2], github.com/jsteenb2/errors/errors_stack_traces_test.go:250[TestE_Format.func2] ]
258+
[ github.com/jsteenb2/errors/errors_stack_traces_test.go:247[TestE_Format.func2] ]`
259+
eq(t, want, fmt.Sprintf("%v", err))
260+
eq(t, want, fmt.Sprintf("%s", err))
261+
262+
want = "\"2 errors occurred:\\n\\t* simple [ github.com/jsteenb2/errors/errors_stack_traces_test.go:248[TestE_Format.func2] ]\\n\\t* outter msg: deep [ github.com/jsteenb2/errors/errors_stack_traces_test.go:249[TestE_Format.func2], github.com/jsteenb2/errors/errors_stack_traces_test.go:250[TestE_Format.func2] ]\\n\""
263+
eq(t, want, fmt.Sprintf("%q", err))
264+
})
265+
}

frame.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"strings"
1010
)
1111

12+
const fmtInline = 'i'
13+
1214
// Frame is a single step in stack trace.
1315
type Frame struct {
1416
FilePath string
@@ -38,18 +40,22 @@ func (f Frame) Format(s fmt.State, verb rune) {
3840
case 's':
3941
switch {
4042
case s.Flag('+'):
41-
io.WriteString(s, f.Fn)
42-
io.WriteString(s, "\n\t")
43-
io.WriteString(s, f.FilePath)
43+
io.WriteString(s, f.String())
4444
default:
4545
io.WriteString(s, path.Base(f.FilePath))
4646
}
4747
case 'd':
4848
io.WriteString(s, strconv.Itoa(f.Line))
4949
case 'n':
5050
io.WriteString(s, funcname(f.Fn))
51+
case fmtInline:
52+
io.WriteString(s, f.String())
5153
case 'v':
52-
f.Format(s, 's')
54+
if s.Flag('+') {
55+
f.Format(s, 's')
56+
return
57+
}
58+
io.WriteString(s, path.Base(f.FilePath))
5359
io.WriteString(s, ":")
5460
f.Format(s, 'd')
5561
}
@@ -83,10 +89,12 @@ func (f StackFrames) String() string {
8389
// Format formats the frame according to the fmt.Formatter interface.
8490
// See Frame.Format for the formatting rules.
8591
func (f StackFrames) Format(s fmt.State, verb rune) {
92+
io.WriteString(s, "[ ")
93+
defer func() { io.WriteString(s, " ]") }()
8694
for i, frame := range f {
8795
frame.Format(s, verb)
8896
if i < len(f)-1 {
89-
io.WriteString(s, "\n\n")
97+
io.WriteString(s, ", ")
9098
}
9199
}
92100
}

frame_test.go

Lines changed: 28 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,38 +16,27 @@ func TestStackTrace_SimpleError(t *testing.T) {
1616
eq(t, "[ github.com/jsteenb2/errors/frame_test.go:11[TestStackTrace_SimpleError] ]", frames.String())
1717

1818
sVal := fmt.Sprintf("%s", frames)
19-
eq(t, "frame_test.go", sVal)
19+
eq(t, "[ frame_test.go ]", sVal)
2020

2121
sVal = fmt.Sprintf("%s", frames[0])
2222
eq(t, "frame_test.go", sVal)
2323

24-
wantSPlusVal := `github.com/jsteenb2/errors_test.TestStackTrace_SimpleError
25-
github.com/jsteenb2/errors/frame_test.go`
26-
27-
sPlusVal := fmt.Sprintf("%+s", frames)
28-
eq(t, wantSPlusVal, sPlusVal)
24+
wantSPlusVal := `github.com/jsteenb2/errors/frame_test.go:11[TestStackTrace_SimpleError]`
2925

30-
sPlusVal = fmt.Sprintf("%+s", frames[0])
31-
eq(t, wantSPlusVal, sPlusVal)
26+
eq(t, "[ "+wantSPlusVal+" ]", fmt.Sprintf("%+s", frames))
27+
eq(t, wantSPlusVal, fmt.Sprintf("%+s", frames[0]))
3228

3329
wantLine := "11"
34-
dVal := fmt.Sprintf("%d", frames)
35-
eq(t, wantLine, dVal)
36-
dVal = fmt.Sprintf("%d", frames[0])
37-
eq(t, wantLine, dVal)
30+
eq(t, "[ "+wantLine+" ]", fmt.Sprintf("%d", frames))
31+
eq(t, wantLine, fmt.Sprintf("%d", frames[0]))
3832

3933
wantFile := "frame_test.go:" + wantLine
40-
vVal := fmt.Sprintf("%v", frames)
41-
eq(t, wantFile, vVal)
42-
vVal = fmt.Sprintf("%v", frames[0])
43-
eq(t, wantFile, vVal)
44-
45-
wantVPlusVal := `github.com/jsteenb2/errors_test.TestStackTrace_SimpleError
46-
github.com/jsteenb2/errors/` + wantFile
47-
vPlusVal := fmt.Sprintf("%+v", frames)
48-
eq(t, wantVPlusVal, vPlusVal)
49-
vPlusVal = fmt.Sprintf("%+v", frames[0])
50-
eq(t, wantVPlusVal, vPlusVal)
34+
eq(t, "[ "+wantFile+" ]", fmt.Sprintf("%v", frames))
35+
eq(t, wantFile, fmt.Sprintf("%v", frames[0]))
36+
37+
wantVPlusVal := `github.com/jsteenb2/errors/` + wantFile + `[TestStackTrace_SimpleError]`
38+
eq(t, "[ "+wantVPlusVal+" ]", fmt.Sprintf("%+v", frames))
39+
eq(t, wantVPlusVal, fmt.Sprintf("%+v", frames[0]))
5140
}
5241

5342
func TestStackTrace_WrappedError(t *testing.T) {
@@ -58,39 +47,30 @@ func TestStackTrace_WrappedError(t *testing.T) {
5847
frames := errors.StackTrace(err)
5948
must(t, eqLen(t, 2, frames))
6049

61-
wantStr := "[ github.com/jsteenb2/errors/frame_test.go:54[TestStackTrace_WrappedError], github.com/jsteenb2/errors/frame_test.go:55[TestStackTrace_WrappedError] ]"
50+
wantStr := "[ github.com/jsteenb2/errors/frame_test.go:43[TestStackTrace_WrappedError], github.com/jsteenb2/errors/frame_test.go:44[TestStackTrace_WrappedError] ]"
6251
eq(t, wantStr, frames.String())
6352

64-
eq(t, "frame_test.go\n\nframe_test.go", fmt.Sprintf("%s", frames))
53+
eq(t, "[ frame_test.go, frame_test.go ]", fmt.Sprintf("%s", frames))
6554
eq(t, "frame_test.go", fmt.Sprintf("%s", frames[0]))
6655
eq(t, "frame_test.go", fmt.Sprintf("%s", frames[1]))
6756

68-
wantSPlusVal := `github.com/jsteenb2/errors_test.TestStackTrace_WrappedError
69-
github.com/jsteenb2/errors/frame_test.go
70-
71-
github.com/jsteenb2/errors_test.TestStackTrace_WrappedError
72-
github.com/jsteenb2/errors/frame_test.go`
57+
wantSPlusVal := `[ github.com/jsteenb2/errors/frame_test.go:43[TestStackTrace_WrappedError], github.com/jsteenb2/errors/frame_test.go:44[TestStackTrace_WrappedError] ]`
7358
eq(t, wantSPlusVal, fmt.Sprintf("%+s", frames))
7459

75-
wantSPlusVal = `github.com/jsteenb2/errors_test.TestStackTrace_WrappedError
76-
github.com/jsteenb2/errors/frame_test.go`
77-
eq(t, wantSPlusVal, fmt.Sprintf("%+s", frames[0]))
78-
eq(t, wantSPlusVal, fmt.Sprintf("%+s", frames[1]))
79-
80-
eq(t, "54\n\n55", fmt.Sprintf("%d", frames))
81-
eq(t, "54", fmt.Sprintf("%d", frames[0]))
82-
eq(t, "55", fmt.Sprintf("%d", frames[1]))
60+
eq(t, `github.com/jsteenb2/errors/frame_test.go:43[TestStackTrace_WrappedError]`, fmt.Sprintf("%+s", frames[0]))
61+
eq(t, `github.com/jsteenb2/errors/frame_test.go:44[TestStackTrace_WrappedError]`, fmt.Sprintf("%+s", frames[1]))
8362

84-
wantFile := `frame_test.go:54
63+
eq(t, "[ 43, 44 ]", fmt.Sprintf("%d", frames))
64+
eq(t, "43", fmt.Sprintf("%d", frames[0]))
65+
eq(t, "44", fmt.Sprintf("%d", frames[1]))
8566

86-
frame_test.go:55`
67+
wantFile := `[ frame_test.go:43, frame_test.go:44 ]`
8768
eq(t, wantFile, fmt.Sprintf("%v", frames))
88-
eq(t, "frame_test.go:54", fmt.Sprintf("%v", frames[0]))
89-
eq(t, "frame_test.go:55", fmt.Sprintf("%v", frames[1]))
90-
91-
wantVPlusFrame := `github.com/jsteenb2/errors_test.TestStackTrace_WrappedError
92-
github.com/jsteenb2/errors/frame_test.go`
93-
eq(t, wantVPlusFrame+":54\n\n"+wantVPlusFrame+":55", fmt.Sprintf("%+v", frames))
94-
eq(t, wantVPlusFrame+":54", fmt.Sprintf("%+v", frames[0]))
95-
eq(t, wantVPlusFrame+":55", fmt.Sprintf("%+v", frames[1]))
69+
eq(t, "frame_test.go:43", fmt.Sprintf("%v", frames[0]))
70+
eq(t, "frame_test.go:44", fmt.Sprintf("%v", frames[1]))
71+
72+
wantVPlusFrame := `[ github.com/jsteenb2/errors/frame_test.go:43[TestStackTrace_WrappedError], github.com/jsteenb2/errors/frame_test.go:44[TestStackTrace_WrappedError] ]`
73+
eq(t, wantVPlusFrame, fmt.Sprintf("%+v", frames))
74+
eq(t, "github.com/jsteenb2/errors/frame_test.go:43[TestStackTrace_WrappedError]", fmt.Sprintf("%+v", frames[0]))
75+
eq(t, "github.com/jsteenb2/errors/frame_test.go:44[TestStackTrace_WrappedError]", fmt.Sprintf("%+v", frames[1]))
9676
}

join.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"cmp"
55
"errors"
66
"fmt"
7+
"io"
78
"strings"
89
)
910

@@ -73,6 +74,18 @@ func (err *joinE) Error() string {
7374
return err.formatFn(err.msg, err.errs)
7475
}
7576

77+
func (err *joinE) Format(s fmt.State, verb rune) {
78+
switch verb {
79+
case 'v':
80+
fallthrough
81+
case 's':
82+
io.WriteString(s, err.Error())
83+
err.stackTrace().Format(s, fmtInline)
84+
case 'q':
85+
fmt.Fprintf(s, "%q", err.Error())
86+
}
87+
}
88+
7689
func (err *joinE) Fields() []any {
7790
var (
7891
out []any

join_test.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ func TestJoin(t *testing.T) {
1515
err := errors.Join(errors.New("first multi error"))
1616

1717
gotMsg := err.Error()
18-
eq(t, "1 error occurred:\n\t* first multi error\n", gotMsg)
18+
wantMsg := `1 error occurred:
19+
* first multi error [ github.com/jsteenb2/errors/join_test.go:15[TestJoin.func1] ]
20+
`
21+
eq(t, wantMsg, gotMsg)
1922

2023
unwrappedErr := errors.Unwrap(err)
2124
if unwrappedErr == nil {
@@ -33,8 +36,8 @@ func TestJoin(t *testing.T) {
3336
)
3437

3538
wantMsg := `2 errors occurred:
36-
* err 1
37-
* err 2
39+
* err 1 [ github.com/jsteenb2/errors/join_test.go:34[TestJoin.func2] ]
40+
* err 2 [ github.com/jsteenb2/errors/join_test.go:35[TestJoin.func2] ]
3841
`
3942
eq(t, wantMsg, err.Error())
4043

@@ -58,7 +61,7 @@ func TestJoin(t *testing.T) {
5861
)
5962

6063
wantMsg := `2 errors occurred:
61-
* err 1
64+
* err 1 [ github.com/jsteenb2/errors/join_test.go:59[TestJoin.func3] ]
6265
* sentinel err
6366
`
6467
eq(t, wantMsg, err.Error())
@@ -83,21 +86,21 @@ func TestJoin(t *testing.T) {
8386
)
8487
wantFields := []any{
8588
// parent Join error
86-
"kj1", "vj1", "err_kind", "foo", "stack_trace", []string{"github.com/jsteenb2/errors/join_test.go:75[TestJoin.func4]"},
89+
"kj1", "vj1", "err_kind", "foo", "stack_trace", []string{"github.com/jsteenb2/errors/join_test.go:78[TestJoin.func4]"},
8790
// first err
88-
"err_0", []any{"ki1", "vi1", "err_kind", "foo", "stack_trace", []string{"github.com/jsteenb2/errors/join_test.go:76[TestJoin.func4]"}},
91+
"err_0", []any{"ki1", "vi1", "err_kind", "foo", "stack_trace", []string{"github.com/jsteenb2/errors/join_test.go:79[TestJoin.func4]"}},
8992
// third err
90-
"err_2", []any{"ki3", "vi3", "stack_trace", []string{"github.com/jsteenb2/errors/join_test.go:78[TestJoin.func4]"}},
93+
"err_2", []any{"ki3", "vi3", "stack_trace", []string{"github.com/jsteenb2/errors/join_test.go:81[TestJoin.func4]"}},
9194
// fourth err
9295
"err_3", []any{
93-
"stack_trace", []string{"github.com/jsteenb2/errors/join_test.go:79[TestJoin.func4]"},
94-
"err_0", []any{"stack_trace", []string{"github.com/jsteenb2/errors/join_test.go:80[TestJoin.func4]"}},
96+
"stack_trace", []string{"github.com/jsteenb2/errors/join_test.go:82[TestJoin.func4]"},
97+
"err_0", []any{"stack_trace", []string{"github.com/jsteenb2/errors/join_test.go:83[TestJoin.func4]"}},
9598
},
9699
}
97100
eqFields(t, wantFields, errors.Fields(err))
98101

99102
unwrapped := errors.Unwrap(err)
100-
wantFields = []any{"ki1", "vi1", "err_kind", "foo", "stack_trace", []string{"github.com/jsteenb2/errors/join_test.go:76[TestJoin.func4]"}}
103+
wantFields = []any{"ki1", "vi1", "err_kind", "foo", "stack_trace", []string{"github.com/jsteenb2/errors/join_test.go:79[TestJoin.func4]"}}
101104
eqFields(t, wantFields, errors.Fields(unwrapped))
102105

103106
sentinelUnwrapped := errors.Unwrap(unwrapped)

0 commit comments

Comments
 (0)