-
Notifications
You must be signed in to change notification settings - Fork 1
/
err_impl.go
198 lines (179 loc) · 4.37 KB
/
err_impl.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package errors
import (
"cmp"
"fmt"
"io"
)
func newE(opts ...any) error {
var err e
skipFrames := FrameSkips(3)
for _, o := range opts {
if o == nil {
continue
}
switch arg := o.(type) {
case string:
err.msg = arg
case FrameSkips:
if arg == NoFrame {
skipFrames = NoFrame
} else if skipFrames != NoFrame {
skipFrames += +arg
}
case Kind:
err.kind = arg
case KV:
err.kvs = append(err.kvs, arg)
case []KV:
err.kvs = append(err.kvs, arg...)
case error:
err.wrappedErr = arg
}
}
if frame, ok := getFrame(skipFrames); ok {
err.frame = frame
}
return &err
}
// e represents the internal error. We do not expose this error to avoid
// hyrum's law. We wish for folks to sink their teeth into the behavior
// of this error, via the exported funcs, or w/e interface a consumer
// wishes to create.
// TODO:
// 1. add Formatter implementation
type e struct {
msg string
frame Frame
kind Kind
wrappedErr error
// TODO:
// 1. should kvs be a map instead? aka unique by key name?
// * if unique by name... what to do with collisions, last write wins? combine values into slice?
// or have some other way to signal what to do with collisions via an additional option?
// 2. if slice of KVs, do we separate the stack frames from the output when
// calling something like Meta/Fields on the error? Then have a specific
// function for getting the logging fields (i.e. everything to []any)
kvs []KV
}
func (err *e) Error() string {
msg := err.msg
if err.wrappedErr != nil {
if msg != "" {
msg += ": "
}
msg += err.wrappedErr.Error()
}
return msg
}
func (err *e) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
fallthrough
case 's':
io.WriteString(s, err.Error()+" ")
err.stackTrace().Format(s, fmtInline)
case 'q':
fmt.Fprintf(s, "%q", err.Error())
}
}
// Fields represents the meaningful logging fields from the error. These include
// all the KVs, error kind, and stack trace for the error and all wrapped error
// fields. This is an incredibly powerful tool to enhance the logging/observability
// for errors.
//
// TODO:
// - decide how to handle duplicate kvs, for the time being, allow duplicates
// - decide on name for this method, Fields is mostly referring to the fields
// that are useful in the context of logging, where contextual metadata from
// the error can eliminate large swathes of the DEBUG/Log driven debugging.
func (err *e) Fields() []any {
var (
out []any
kind Kind
)
for err := error(err); err != nil; err = Unwrap(err) {
em := getErrMeta(err)
for _, kv := range em.kvs {
out = append(out, kv.K, kv.V)
}
kind = cmp.Or(kind, em.kind)
if ej, ok := err.(*joinE); ok {
innerKind, multiErrFields := ej.subErrFields()
kind = cmp.Or(kind, innerKind)
if len(multiErrFields) > 0 {
out = append(out, "multi_err", multiErrFields)
}
break
}
}
if kind != "" {
out = append(out, "err_kind", string(kind))
}
if stackFrames := err.stackTrace(); len(stackFrames) > 0 {
var simplified []string
for _, frame := range stackFrames {
simplified = append(simplified, frame.String())
}
out = append(out, "stack_trace", simplified)
}
return out
}
func (err *e) Is(target error) bool {
kind, ok := target.(Kind)
return ok && err.kind == kind
}
func (err *e) Unwrap() error {
return err.wrappedErr
}
func (err *e) V(key string) (any, bool) {
for err := error(err); err != nil; err = Unwrap(err) {
for _, kv := range getErrMeta(err).kvs {
if kv.K == key {
return kv.V, true
}
}
}
return nil, false
}
func (err *e) stackTrace() StackFrames {
var out StackFrames
for err := error(err); err != nil; err = Unwrap(err) {
em := getErrMeta(err)
if em.frame.FilePath == "" {
continue
}
out = append(out, em.frame)
if em.errType == errTypeJoin {
break
}
}
return out
}
type errMeta struct {
kind Kind
frame Frame
kvs []KV
errType string
}
const (
errTypeE = "e"
errTypeJoin = "j"
)
func getErrMeta(err error) errMeta {
var em errMeta
switch err := err.(type) {
case *e:
em.kind, em.frame, em.kvs, em.errType = err.kind, err.frame, err.kvs, errTypeE
case *joinE:
em.kind, em.frame, em.kvs, em.errType = err.kind, err.frame, err.kvs, errTypeJoin
}
return em
}
func getKind(err error) Kind {
for ; err != nil; err = Unwrap(err) {
if em := getErrMeta(err); em.kind != "" {
return em.kind
}
}
return ""
}