-
Notifications
You must be signed in to change notification settings - Fork 2
/
errors.go
391 lines (343 loc) · 10.5 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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
package errors
import (
"bytes"
"errors" // revive:disable-line:imports-blacklist
"fmt"
"io"
"runtime"
"strings"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/status"
)
// stack is a comparable []uintptr slice.
type stack struct {
frames []uintptr
}
// An altiplaError annotates a cause error with a stacktrace and an explanatory
// message.
type altiplaError struct {
// The cause error.
cause error
// The previous altiplaError, if any.
previous *altiplaError
// The current stacktrace. Might be the same as previous' stacktrace if that
// is another altiplaError.
stack *stack
// A small explanatory message what went wrong at this level in the stack.
reason string
// The index of the stack frame where this altiplaError was added.
index int
}
// Error implements error, and outputs a full backtrace.
func (e *altiplaError) Error() string {
return e.cause.Error()
}
// Cause implements important interfaces in other libs to detect the cause of an error.
func (e *altiplaError) Cause() error {
return e.cause
}
// GRPCStatus allows us to wrap GRPC errors without losing the original error
// code and retaining the ability to compare it.
func (e *altiplaError) GRPCStatus() *status.Status {
return status.Convert(e.cause)
}
// A Frame represents a Frame in an altipla callstack. The Reason is the manual
// annotation passed to altipla.Wrapf.
type Frame struct {
File string
Function string
Line int
Reason string
}
type stackWithReasons struct {
stack *stack
reasons []string
}
// Frames extracts all frames from an altipla error. If err is not an altipla error,
// nil is returned.
func Frames(err error) [][]Frame {
e, ok := err.(*altiplaError)
if !ok {
return nil
}
// Walk the chain of altiplaErrors backwards, collecting a set of stacks and
// reasons.
stacks := make([]stackWithReasons, 0, 8)
for ; e != nil; e = e.previous {
// If the current error's stack is different from the previous, add it to
// the set of stacks.
if len(stacks) == 0 || stacks[len(stacks)-1].stack != e.stack {
stacks = append(stacks, stackWithReasons{
stack: e.stack,
reasons: make([]string, len(e.stack.frames)),
})
}
// Store the reason with its stack frame.
stacks[len(stacks)-1].reasons[e.index] = e.reason
}
parsedStacks := make([][]Frame, 0, len(stacks))
// Walk the set of stacks backwards, starting with stack most closest to the
// cause error.
for i := len(stacks) - 1; i >= 0; i-- {
frames := stacks[i].stack.frames
reasons := stacks[i].reasons
parsedFrames := make([]Frame, 0, 8)
// Iterate over the stack frames.
iter := runtime.CallersFrames(frames)
// j tracks the index in the combined frames / reasons array of iter' stack
// frame. Each frame in frames / reasons array appears at least once in the
// iterator's frames, but the iterator's frame might have more frames (for
// example, cgo frames, or inlined frames.)
j := 0
for {
frame, ok := iter.Next()
if !ok {
break
}
// Advance j and load the reason whenever the current iterator's frame
// matches. The iterator's frame's PC might differ by 1 because the
// iterator adjusts for the difference between callsite and return
// address.
var reason string
if j < len(frames) && (frame.PC == frames[j] || frame.PC+1 == frames[j]) {
reason = reasons[j]
j++
}
file := frame.File
i := strings.LastIndex(file, "/src/")
if i >= 0 {
file = file[i+len("/src/"):]
}
parsedFrames = append(parsedFrames, Frame{
File: file,
Function: frame.Function,
Line: frame.Line,
Reason: reason,
})
}
parsedStacks = append(parsedStacks, parsedFrames)
}
return parsedStacks
}
// writeStackTrace unwinds a chain of altiplaErrors and prints the stacktrace
// annotated with explanatory messages.
func (e *altiplaError) writeStackTrace(w io.Writer) {
fmt.Fprintf(w, "%s\n\n", e.cause.Error())
for i, stack := range Frames(e) {
// Include a newline between stacks.
if i > 0 {
fmt.Fprintf(w, "\n")
}
for _, frame := range stack {
// Print the current function.
if frame.Reason != "" {
fmt.Fprintf(w, "%s: %s\n", frame.Function, frame.Reason)
} else {
fmt.Fprintf(w, "%s\n", frame.Function)
}
fmt.Fprintf(w, "\t%s:%d\n", frame.File, frame.Line)
}
}
}
// Unwrap implements the standard for unwrappable errors.
func (e *altiplaError) Unwrap() error {
return e.cause
}
func Details(err error) string {
e, ok := err.(*altiplaError)
if !ok {
return "{" + err.Error() + "}"
}
result := []string{
"{" + e.cause.Error() + "}",
}
for i, stack := range Frames(e) {
if i > 0 {
result = append(result, "{-----}")
}
for _, frame := range stack {
if frame.Reason != "" {
result = append(result, fmt.Sprintf("{%s:%d:%s: %s}", frame.File, frame.Line, frame.Function, frame.Reason))
} else {
result = append(result, fmt.Sprintf("{%s:%d:%s}", frame.File, frame.Line, frame.Function))
}
}
}
return strings.Join(result, " ")
}
// isPrefix checks if a is a prefix of b.
func isPrefix(a []uintptr, b []uintptr) bool {
if len(a) > len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func internalWrapf(err error, reason string) error {
var cause error
var previous *altiplaError
var st *stack
var index int
found := false
if e, ok := err.(*altiplaError); ok {
cause = e.cause
previous = e
// Figure out where we are in the existing callstack. Since Wrapf isn't
// guaranteed to be called at every stack frame, we need to search to find
// the current callsite. We start searching one level past the previous
// level (and assume that Wrapf is called at most once per stack).
st = e.stack
index = e.index + 1
// To check where we are, match a number of return frames in the stack. We
// check one level deeper than the level we are annotating, because the
// frame in the calling function likely doesn't match:
//
// - parent() calls Wrapf and return an error with a stacktrace - child()
// check cause's return value and then calls Wrapf on parent's error -
// compare() is the frame that gets compared
//
// When parent calls Wrapf and captures the stack frame, the program
// counter in child will point the if statement that checks the parent's
// return value. When the child then calls Wrapf, it's program counter
// will have advanced to the Wrapf call, and will no longer match the
// program originally captured by the parent. However, the program counter
// in compare will still match, and so we compare against that.
//
// To paper over small numbers of dupliate frames (eg. when using
// recursion), we compare not just 1 frame, but several. We compare only
// some frames (instead of all) to keep the runtime of Wrapf efficient.
var buffer [8]uintptr
// 0 is the frame of Callers, 1 is us, 2 is the public wrapper, 3 is its
// caller (child), 4 is the caller's caller (compare).
compare := buffer[:runtime.Callers(4, buffer[:])]
for index+1 < len(st.frames) {
if isPrefix(compare, st.frames[index+1:]) {
found = true
break
}
index++
}
} else {
cause = err
}
if !found {
var buffer [256]uintptr
// 0 is the frame of Callers, 1 is us, 2 is the public wrapper, 3 is its
// caller.
n := runtime.Callers(3, buffer[:])
frames := make([]uintptr, n)
copy(frames, buffer[:n])
index = 0
st = &stack{frames: frames}
}
return &altiplaError{
cause: cause,
previous: previous,
stack: st,
reason: reason,
index: index,
}
}
// Errorf creates a new error with a reason and a stacktrace.
//
// Use Errorf in places where you would otherwise return an error using
// fmt.Errorf or errors.New.
//
// Note that the result of Errorf includes a stacktrace. This means
// that Errorf is not suitable for storing in global variables. For
// such errors, keep using errors.New.
func Errorf(format string, a ...interface{}) error {
return internalWrapf(fmt.Errorf(format, a...), "")
}
// New creates a new error without stacktrace.
func New(err string) error {
return errors.New(err)
}
// Wrapf annotates an error with a reason and a stacktrace. If err is nil,
// Wrapf returns nil.
//
// Use Wrapf in places where you would otherwise return an error directly. If
// the error passed to Wrapf is nil, Wrapf will also return nil. This makes it
// safe to use in one-line return statements.
//
// To check if a wrapped error is a specific error, such as io.EOF, you can
// extract the error passed in to Wrapf using Cause.
func Wrapf(err error, format string, a ...interface{}) error {
if err == nil {
return nil
}
return internalWrapf(err, fmt.Sprintf(format, a...))
}
// Trace annotates an error with a stacktrace.
//
// Use Trace in places where you would otherwise return an error directly. If
// the error passed to Trace is nil, Trace will also return nil. This makes it
// safe to use in one-line return statements.
//
// To check if a wrapped error is a specific error, such as io.EOF, you can
// extract the error passed in to Trace using Cause.
func Trace(err error) error {
if err == nil {
return nil
}
return internalWrapf(err, "")
}
// Cause extracts the cause error of an altipla error. If err is not an altipla
// error, err itself is returned.
//
// You can use Cause to check if an error is an expected error. For example, if
// you know than EOF error is fine, you can handle it with Cause.
func Cause(err error) error {
if e, ok := err.(*altiplaError); ok {
return e.cause
}
return err
}
// Is reports whether err or its cause matches target.
func Is(err, target error) bool {
if target == nil {
return err == target
}
if err == target {
return true
}
if e, ok := err.(*altiplaError); ok {
return e.cause == target
}
return false
}
// As finds the first error in err's chain that matches target, and if so, sets
// target to that error value and returns true.
func As(err error, target interface{}) bool {
return errors.As(err, target)
}
// Recover recovers from a panic in a defer. If there is no panic, Recover()
// returns nil. To use, call error.Recover(recover()) and compare the result to nil.
func Recover(p interface{}) error {
if p == nil {
return nil
}
if err, ok := p.(error); ok {
return internalWrapf(err, "recovered panic")
}
return internalWrapf(fmt.Errorf("recovered panic: %v", p), "")
}
func LogFields(err error) log.Fields {
return log.Fields{
"error": err.Error(),
"details": Details(err),
}
}
func Stack(err error) string {
e, ok := err.(*altiplaError)
if !ok {
return err.Error()
}
var buffer bytes.Buffer
e.writeStackTrace(&buffer)
return buffer.String()
}