blob: 60062a4372bc85048cec6646bc44f1304ada4cd6 [file] [log] [blame]
Scott Baker1fe72732019-10-21 10:58:51 -07001// Package errors provides errors that have stack-traces.
2//
3// This is particularly useful when you want to understand the
4// state of execution when an error was returned unexpectedly.
5//
6// It provides the type *Error which implements the standard
7// golang error interface, so you can use this library interchangably
8// with code that is expecting a normal error return.
9//
10// For example:
11//
12// package crashy
13//
14// import "github.com/go-errors/errors"
15//
16// var Crashed = errors.Errorf("oh dear")
17//
18// func Crash() error {
19// return errors.New(Crashed)
20// }
21//
22// This can be called as follows:
23//
24// package main
25//
26// import (
27// "crashy"
28// "fmt"
29// "github.com/go-errors/errors"
30// )
31//
32// func main() {
33// err := crashy.Crash()
34// if err != nil {
35// if errors.Is(err, crashy.Crashed) {
36// fmt.Println(err.(*errors.Error).ErrorStack())
37// } else {
38// panic(err)
39// }
40// }
41// }
42//
43// This package was original written to allow reporting to Bugsnag,
44// but after I found similar packages by Facebook and Dropbox, it
45// was moved to one canonical location so everyone can benefit.
46package errors
47
48import (
49 "bytes"
50 "fmt"
51 "reflect"
52 "runtime"
53)
54
55// The maximum number of stackframes on any error.
56var MaxStackDepth = 50
57
58// Error is an error with an attached stacktrace. It can be used
59// wherever the builtin error interface is expected.
60type Error struct {
61 Err error
62 stack []uintptr
63 frames []StackFrame
64 prefix string
65}
66
67// New makes an Error from the given value. If that value is already an
68// error then it will be used directly, if not, it will be passed to
69// fmt.Errorf("%v"). The stacktrace will point to the line of code that
70// called New.
71func New(e interface{}) *Error {
72 var err error
73
74 switch e := e.(type) {
75 case error:
76 err = e
77 default:
78 err = fmt.Errorf("%v", e)
79 }
80
81 stack := make([]uintptr, MaxStackDepth)
82 length := runtime.Callers(2, stack[:])
83 return &Error{
84 Err: err,
85 stack: stack[:length],
86 }
87}
88
89// Wrap makes an Error from the given value. If that value is already an
90// error then it will be used directly, if not, it will be passed to
91// fmt.Errorf("%v"). The skip parameter indicates how far up the stack
92// to start the stacktrace. 0 is from the current call, 1 from its caller, etc.
93func Wrap(e interface{}, skip int) *Error {
94 var err error
95
96 switch e := e.(type) {
97 case *Error:
98 return e
99 case error:
100 err = e
101 default:
102 err = fmt.Errorf("%v", e)
103 }
104
105 stack := make([]uintptr, MaxStackDepth)
106 length := runtime.Callers(2+skip, stack[:])
107 return &Error{
108 Err: err,
109 stack: stack[:length],
110 }
111}
112
113// WrapPrefix makes an Error from the given value. If that value is already an
114// error then it will be used directly, if not, it will be passed to
115// fmt.Errorf("%v"). The prefix parameter is used to add a prefix to the
116// error message when calling Error(). The skip parameter indicates how far
117// up the stack to start the stacktrace. 0 is from the current call,
118// 1 from its caller, etc.
119func WrapPrefix(e interface{}, prefix string, skip int) *Error {
120
121 err := Wrap(e, 1+skip)
122
123 if err.prefix != "" {
124 prefix = fmt.Sprintf("%s: %s", prefix, err.prefix)
125 }
126
127 return &Error{
128 Err: err.Err,
129 stack: err.stack,
130 prefix: prefix,
131 }
132
133}
134
135// Is detects whether the error is equal to a given error. Errors
136// are considered equal by this function if they are the same object,
137// or if they both contain the same error inside an errors.Error.
138func Is(e error, original error) bool {
139
140 if e == original {
141 return true
142 }
143
144 if e, ok := e.(*Error); ok {
145 return Is(e.Err, original)
146 }
147
148 if original, ok := original.(*Error); ok {
149 return Is(e, original.Err)
150 }
151
152 return false
153}
154
155// Errorf creates a new error with the given message. You can use it
156// as a drop-in replacement for fmt.Errorf() to provide descriptive
157// errors in return values.
158func Errorf(format string, a ...interface{}) *Error {
159 return Wrap(fmt.Errorf(format, a...), 1)
160}
161
162// Error returns the underlying error's message.
163func (err *Error) Error() string {
164
165 msg := err.Err.Error()
166 if err.prefix != "" {
167 msg = fmt.Sprintf("%s: %s", err.prefix, msg)
168 }
169
170 return msg
171}
172
173// Stack returns the callstack formatted the same way that go does
174// in runtime/debug.Stack()
175func (err *Error) Stack() []byte {
176 buf := bytes.Buffer{}
177
178 for _, frame := range err.StackFrames() {
179 buf.WriteString(frame.String())
180 }
181
182 return buf.Bytes()
183}
184
185// Callers satisfies the bugsnag ErrorWithCallerS() interface
186// so that the stack can be read out.
187func (err *Error) Callers() []uintptr {
188 return err.stack
189}
190
191// ErrorStack returns a string that contains both the
192// error message and the callstack.
193func (err *Error) ErrorStack() string {
194 return err.TypeName() + " " + err.Error() + "\n" + string(err.Stack())
195}
196
197// StackFrames returns an array of frames containing information about the
198// stack.
199func (err *Error) StackFrames() []StackFrame {
200 if err.frames == nil {
201 err.frames = make([]StackFrame, len(err.stack))
202
203 for i, pc := range err.stack {
204 err.frames[i] = NewStackFrame(pc)
205 }
206 }
207
208 return err.frames
209}
210
211// TypeName returns the type this error. e.g. *errors.stringError.
212func (err *Error) TypeName() string {
213 if _, ok := err.Err.(uncaughtPanic); ok {
214 return "panic"
215 }
216 return reflect.TypeOf(err.Err).String()
217}