-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
errors.go
170 lines (145 loc) · 3.77 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
// Package errors adds some useful error helpers.
package errors
import (
"errors"
"fmt"
"runtime"
"strings"
"sync"
)
var (
// Package to add trace lines for; if blank all traces are added.
Package string
// StackSize is the maximum stack sized added to errors. Set to 0 to
// disable.
StackSize int = 32
)
func New(text string) error { return addStack(errors.New(text)) }
func Errorf(f string, a ...interface{}) error { return addStack(fmt.Errorf(f, a...)) }
func Unwrap(err error) error { return errors.Unwrap(err) }
func Is(err, target error) bool { return errors.Is(err, target) }
func As(err error, target interface{}) bool { return errors.As(err, target) }
// Wrap an error with fmt.Errorf(), returning nil if err is nil.
func Wrap(err error, s string) error {
if err == nil {
return nil
}
return addStack(fmt.Errorf(s+": %w", err))
}
// Wrapf an error with fmt.Errorf(), returning nil if err is nil.
func Wrapf(err error, format string, a ...interface{}) error {
if err == nil {
return nil
}
return addStack(fmt.Errorf(format+": %w", append(a, err)...))
}
func addStack(err error) error {
if StackSize == 0 {
return err
}
pc := make([]uintptr, StackSize)
n := runtime.Callers(3, pc)
pc = pc[:n]
frames := runtime.CallersFrames(pc)
var b strings.Builder
for {
frame, more := frames.Next()
if Package != "" && !strings.HasPrefix(frame.Function, Package) {
if !more {
break
}
continue
}
// Don't format exactly the same as debug.PrintStack(); memory addresses
// aren't very useful here and only add to the noise.
b.WriteString(fmt.Sprintf("\t%s()\n\t\t%s:%d\n", frame.Function, frame.File, frame.Line))
if !more {
break
}
}
return &StackErr{err: err, stack: b.String()}
}
type StackTracer interface {
StackTrace() string
}
type StackErr struct {
stack string
err error
}
func (err StackErr) Unwrap() error { return err.err }
func (err StackErr) StackTrace() string { return err.stack }
func (err StackErr) Error() string {
if err.stack == "" {
return fmt.Sprintf("%s", err.err)
}
return fmt.Sprintf("%s\n%s", err.err, err.stack)
}
// Group multiple errors.
type Group struct {
// Maximum number of errors; calls to Append() won't do anything if the
// number of errors is larger than this.
MaxSize int
mu *sync.Mutex
errs []error
nerrs int
}
// NewGroup create a new Group instance. It will record a maximum of maxSize
// errors. Set to 0 for no limit.
func NewGroup(maxSize int) *Group {
return &Group{MaxSize: maxSize, mu: new(sync.Mutex)}
}
func (g Group) Error() string {
if len(g.errs) == 0 {
return ""
}
var b strings.Builder
if g.nerrs > len(g.errs) {
fmt.Fprintf(&b, "%d errors (first %d shown):\n", g.nerrs, len(g.errs))
} else {
fmt.Fprintf(&b, "%d errors:\n", len(g.errs))
}
for _, e := range g.errs {
if e2, ok := e.(*StackErr); ok {
e = e2.Unwrap()
}
b.WriteString(e.Error())
b.WriteByte('\n')
}
return b.String()
}
// Len returns the number of errors.
func (g Group) Len() int { return len(g.errs) }
// Append a new error to the list; this is thread-safe.
//
// It won't do anything if the error is nil, in which case it will return false.
// This makes appending errors in a loop slightly nicer:
//
// for {
// err := do()
// if errors.Append(err) {
// continue
// }
// }
func (g *Group) Append(err error) bool {
if err == nil {
return false
}
g.mu.Lock()
defer g.mu.Unlock()
g.nerrs++
if g.MaxSize == 0 || len(g.errs) < g.MaxSize {
g.errs = append(g.errs, err)
}
return true
}
// ErrorOrNil returns itself if there are errors, or nil otherwise.
//
// It avoids an if-check at the end:
//
// return errs.ErrorOrNil()
func (g *Group) ErrorOrNil() error {
if g.Len() == 0 {
return nil
}
return g
}