forked from nuclio/errors
-
Notifications
You must be signed in to change notification settings - Fork 0
/
errors.go
292 lines (242 loc) · 6.33 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
// Package errors provides an api similar to github.com/nuclio/nuclio/pkg/errors
// However we don't carry stack trace around for performance
// (see https://github.com/pkg/errors/issues/124)
package errors
// All error values returned from this package implement fmt.Formatter and can
// be formatted by the fmt package. The following verbs are supported
//
// %s print the error
// %+v extended format. Will print stack trace of errors
import (
"bytes"
"fmt"
"io"
"os"
"reflect"
"runtime"
"strings"
)
var (
// ShowLineInfo sets if we collect location information (file, line)
// (getting location information makes creating error slower ~550ns vs 2ns)
ShowLineInfo bool
)
// Error implements error interface with call stack
type Error struct {
message string
cause error
fileName string
lineNumber int
}
func init() {
ShowLineInfo = len(os.Getenv("NUCLIO_NO_ERROR_LINE_INFO")) == 0
}
// caller return the caller informatin (file, line)
// Note this is sensitive to where it's called
func caller() (string, int) {
pcs := make([]uintptr, 1)
// skip 3 levels to get to the caller
n := runtime.Callers(3, pcs)
if n == 0 {
return "", 0
}
pc := pcs[0] - 1
fn := runtime.FuncForPC(pc)
if fn == nil {
return "", 0
}
return fn.FileLine(pc)
}
// New returns a new error
func New(message string) error {
err := &Error{message: message}
if ShowLineInfo {
err.fileName, err.lineNumber = caller()
}
return err
}
// Errorf returns a new Error
func Errorf(format string, args ...interface{}) error {
err := &Error{message: fmt.Sprintf(format, args...)}
if ShowLineInfo {
err.fileName, err.lineNumber = caller()
}
return err
}
// Wrap returns a new error with err as cause, if err is nil will return nil
func Wrap(err error, message string) error {
if err == nil {
return nil
}
errObj := &Error{
message: message,
cause: err,
}
if ShowLineInfo {
errObj.fileName, errObj.lineNumber = caller()
}
return errObj
}
// Wrapf returns a new error with err as cause, if err is nil will return nil
func Wrapf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
message := fmt.Sprintf(format, args...)
errObj := &Error{
message: message,
cause: err,
}
if ShowLineInfo {
errObj.fileName, errObj.lineNumber = caller()
}
return errObj
}
// Error is the string representation of the error
func (err *Error) Error() string {
return err.message
}
func (err *Error) Unwrap() error {
return err.cause
}
// Cause returns the cause of the error
func (err *Error) Cause() error {
return err.cause
}
func asError(err error) *Error {
errObj, ok := err.(*Error)
if !ok {
return nil
}
return errObj
}
// LineInfo info returns the location (file, line) where the error was created
func (err *Error) LineInfo() (string, int) {
return err.fileName, err.lineNumber
}
// reverse reverses a slice in place
func reverse(slice []error) {
for left, right := 0, len(slice)-1; left < right; left, right = left+1, right-1 {
slice[left], slice[right] = slice[right], slice[left]
}
}
// GetErrorStack return stack of messges (oldest on top)
// if n == -1 returns the whole stack
func GetErrorStack(err error, depth int) []error {
errors := []error{err}
errObj := asError(err)
if errObj == nil {
return errors
}
for errObj = asError(errObj.cause); errObj != nil; errObj = asError(errObj.cause) {
errors = append(errors, errObj)
}
reverse(errors)
if depth > 0 {
if depth > len(errors) {
depth = len(errors)
}
errors = errors[:depth]
}
return errors
}
// GetErrorStackString returns the error stack as a string
func GetErrorStackString(err error, depth int) string {
buffer := bytes.Buffer{}
PrintErrorStack(&buffer, err, depth)
return buffer.String()
}
// PrintErrorStack prints the error stack into out up to depth levels
// If n == 1 then prints the whole stack
func PrintErrorStack(out io.Writer, err error, depth int) {
if err == nil {
return
}
pathLen := 40
stack := GetErrorStack(err, depth)
errObj := asError(stack[0])
if errObj != nil && errObj.lineNumber != 0 {
cause := errObj.Error()
if errObj.cause != nil {
cause = errObj.cause.Error()
}
fmt.Fprintf(out, "\nError - %s", cause) // nolint: errcheck
fmt.Fprintf(out, "\n %s:%d\n", trimPath(errObj.fileName, pathLen), errObj.lineNumber) // nolint: errcheck
} else {
fmt.Fprintf(out, "\nError - %s", stack[0].Error()) // nolint: errcheck
}
fmt.Fprintf(out, "\nCall stack:") // nolint: errcheck
for _, e := range stack {
errObj := asError(e)
fmt.Fprintf(out, "\n%s", e.Error()) // nolint: errcheck
if errObj != nil && errObj.lineNumber != 0 {
fmt.Fprintf(out, "\n %s:%d", trimPath(errObj.fileName, pathLen), errObj.lineNumber) // nolint: errcheck
}
}
out.Write([]byte{'\n'}) // nolint: errcheck
}
// Cause is the cause of the error
func Cause(err error) error {
var cause error
if err == nil {
return nil
}
errAsError := asError(err)
if errAsError != nil {
cause = errAsError.cause
}
// treat the err as simply an error
if cause == nil {
cause = err
}
return cause
}
// RootCause is the cause of the error
func RootCause(err error) error {
currentErr := err
for {
cause := Cause(currentErr)
// if there's no cause, we're done
// if the cause is not comparabile, it's not an Error and we're done
// if the cause == the error, we're done since that's what Cause() returns
if cause == nil || !reflect.TypeOf(cause).Comparable() || cause == currentErr {
break
}
currentErr = cause
}
return currentErr
}
// sumLengths return sum of lengths of strings
func sumLengths(parts []string) int {
total := 0
for _, s := range parts {
total += len(s)
}
return total
}
// trimPath shortens fileName to be at most size characters
func trimPath(fileName string, size int) string {
if len(fileName) <= size {
return fileName
}
// We'd like to cut at directory boundary
parts := strings.Split(fileName, "/")
for sumLengths(parts) > size && len(parts) > 1 {
parts = parts[1:]
}
return ".../" + strings.Join(parts, "/")
}
// Format formats an error
func (err *Error) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
PrintErrorStack(s, err, -1)
}
fallthrough
case 's':
fmt.Fprintf(s, err.Error()) // nolint: errcheck
case 'q':
fmt.Fprintf(s, "%q", err.Error()) // nolint: errcheck
}
}