-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy patherrors.go
244 lines (212 loc) · 8.24 KB
/
errors.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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
package simplerr
import (
"fmt"
"log/slog"
"slices"
)
// attribute is a key value pair for attributes on errors
type attribute struct {
Key, Value interface{}
}
// SimpleError is an implementation of the `error` interface which provides functionality
// to ease in the operating and handling of errors in applications.
type SimpleError struct {
// parent is the error being wrapped
parent error
// msg is the error message
msg string
// code is the error code of the error defined in the registry
code Code
// silent is a flag that signals that this error should be recorded or logged silently on the server side
// eg. This error should not be logged at all
silent bool
// benign is a flag that signals that, from the server's perspective, this error is a benign error.
// eg. This error can be logged at INFO level and then discarded.
benign bool
// benignReason is the reason this error was marked as "benign"
benignReason string
// retriable is a flag indicating that this error is transient and that the user should retry the operation
retriable bool
// auxiliary are auxiliary informational fields that can be attached to the error
auxiliary map[string]interface{}
// logger is a scoped logger that can be attached to the error
logger *slog.Logger
// attr is a list of custom attributes attached the error
attr []attribute
// stackTrace is the call stack trace for the error
rawStackFrames []uintptr
}
// New creates a new SimpleError from a formatted string
func New(_fmt string, args ...interface{}) *SimpleError {
rawFrames := rawStackFrames(3)
return &SimpleError{msg: fmt.Sprintf(_fmt, args...), code: CodeUnknown, rawStackFrames: rawFrames}
}
// Error satisfies the `error` interface. It uses the `simplerr.Formatter` to generate an error string.
func (e *SimpleError) Error() string {
if parent := e.Unwrap(); parent != nil {
return fmt.Sprintf("%s: %s", e.GetMessage(), parent.Error())
}
return e.msg
}
// Message sets the message text on the error. This message it used to wrap the underlying error, if it exists.
func (e *SimpleError) Message(msg string, args ...interface{}) *SimpleError {
e.msg = fmt.Sprintf(msg, args...)
return e
}
// GetMessage gets the error string for this error, exclusive of any wrapped errors.
func (e *SimpleError) GetMessage() string {
return e.msg
}
// GetCode returns the error code as defined in the registry
func (e *SimpleError) GetCode() Code {
return e.code
}
// Code sets the error code. The assigned code should be defined in the registry.
func (e *SimpleError) Code(code Code) *SimpleError {
e.code = code
return e
}
// Benign marks the error as "benign". A benign error is an error that depends on the context of the caller.
// eg a NotFoundError is only an error if the caller is expecting the entity to exist.
// These errors can usually be logged less severely (ie at INFO rather than ERROR level)
func (e *SimpleError) Benign() *SimpleError {
e.benign = true
return e
}
// BenignReason marks the error as "benign" and attaches a reason it was marked benign.
// A benign error is an error depending on the context of the caller.
// eg a NotFoundError is only an error if the caller is expecting the entity to exist
// These errors can usually be logged less severely (ie at INFO rather than ERROR level)
func (e *SimpleError) BenignReason(reason string) *SimpleError {
e.benign = true
e.benignReason = reason
return e
}
// GetBenignReason returns the benign reason and whether the error was marked as benign
// ie. This error can be logged at INFO level and then discarded.
func (e *SimpleError) GetBenignReason() (string, bool) {
return e.benignReason, e.benign
}
// GetSilent returns a flag that signals that this error should be recorded or logged silently on the server side
// ie. This error should not be logged at all
func (e *SimpleError) GetSilent() bool {
return e.silent
}
// Silence sets the error as silent. Silent errors can be ignored by loggers.
func (e *SimpleError) Silence() *SimpleError {
e.silent = true
return e
}
// GetRetriable returns a flag that signals that the operation which created this error is transient and that the
// user should retry the operation in hopes of it succeeding.
func (e *SimpleError) GetRetriable() bool {
return e.retriable
}
// Retriable sets the error as retriable.
func (e *SimpleError) Retriable() *SimpleError {
e.retriable = true
return e
}
// GetAuxiliary gets the auxiliary informational data attached to this error.
// This key-value data can be attached to structured loggers.
func (e *SimpleError) GetAuxiliary() map[string]interface{} {
return e.auxiliary
}
// GetAttribute gets an attribute attached to this specific SimpleError. It does NOT traverse the error chain.
// This can be used to define attributes on the error that do not have first-class support
// with simplerr. Much like keys in the `context` package, the `key` should be a custom type so it does
// not have naming collisions with other values.
func (e *SimpleError) GetAttribute(key interface{}) (interface{}, bool) {
for _, attr := range e.attr {
if attr.Key == key {
return attr.Value, true
}
}
return nil, false
}
// Aux attaches auxiliary informational data to the error as key value pairs.
// All keys must be of type `string` and have a value. Keys without values are ignored.
// This auxiliary data can be retrieved by using `ExtractAuxiliary()` and attached to structured loggers.
// Do not use this to detect any attributes on the error, instead use Attr().`
func (e *SimpleError) Aux(kv ...interface{}) *SimpleError {
if e.auxiliary == nil {
e.auxiliary = map[string]interface{}{}
}
var key interface{}
for _, item := range kv {
if key == nil {
key = item
continue
}
keyStr, ok := key.(string)
if ok {
e.auxiliary[keyStr] = item
}
key = nil
}
return e
}
// AuxMap attaches auxiliary informational data to the error from a map[string]interface{}.
// This auxiliary data can be retrieved by using `ExtractAuxiliary()` and attached to structured loggers.
// Do not use this to detect any attributes on the error, instead use Attr().`
func (e *SimpleError) AuxMap(aux map[string]interface{}) *SimpleError {
if e.auxiliary == nil {
e.auxiliary = map[string]interface{}{}
}
for k, v := range aux {
e.auxiliary[k] = v
}
return e
}
// Attr attaches an attribute to the error that can be detected when handling the error.
// Attr() behaves similarly to `context.WithValue()`. Keys should be custom types in order to avoid naming collisions.
// Use `GetAttribute()` to get the value of the attribute.
func (e *SimpleError) Attr(key, value interface{}) *SimpleError {
e.attr = append(e.attr, attribute{Key: key, Value: value})
return e
}
// Logger attaches a structured logger to the error
func (e *SimpleError) Logger(l *slog.Logger) *SimpleError {
e.logger = l
return e
}
// GetLogger returns a structured logger with auxiliary information preset
// It uses the first found attached structured logger and otherwise uses the default logger
func (e *SimpleError) GetLogger() *slog.Logger {
l := e.logger
fields := make([]any, 0, 2*len(e.auxiliary))
var errInChain = e
for errInChain != nil {
if l == nil && errInChain.logger != nil {
l = errInChain.logger
}
for k, v := range errInChain.GetAuxiliary() {
fields = append(fields, v, k) // append backwards because of the slice reversal
}
errInChain = As(errInChain.Unwrap())
}
// Reverse the slice so that the aux values at the top of the stack take precedent over the lower ones
// For cases where there are conflicting keys
slices.Reverse(fields)
if l == nil {
l = slog.Default()
}
return l.With(fields...)
}
// GetDescription returns the description of the error code on the error.
func (e *SimpleError) GetDescription() string {
return registry.CodeDescription(e.code)
}
// StackTrace returns the stack trace at the point at which the error was raised.
func (e *SimpleError) StackTrace() []Call {
return stackTrace(e.rawStackFrames)
}
// StackFrames returns a slice of pointers to program counters
// This method is primarily used to better integrate with sentry stack trace extraction
func (e *SimpleError) StackFrames() []uintptr {
return e.rawStackFrames
}
// Unwrap implement the interface required for error unwrapping. It returns the underlying (wrapped) error.
func (e *SimpleError) Unwrap() error {
return e.parent
}