// Package errors provides errors that have stack-traces. // // This is particularly useful when you want to understand the // state of execution when an error was returned unexpectedly. // // It provides the type *Error which implements the standard // golang error interface, so you can use this library interchangably // with code that is expecting a normal error return. // // For example: // // package crashy // // import "github.com/go-errors/errors" // // var Crashed = errors.Errorf("oh dear") // // func Crash() error { // return errors.New(Crashed) // } // // This can be called as follows: // // package main // // import ( // "crashy" // "fmt" // "github.com/go-errors/errors" // ) // // func main() { // err := crashy.Crash() // if err != nil { // if errors.Is(err, crashy.Crashed) { // fmt.Println(err.(*errors.Error).ErrorStack()) // } else { // panic(err) // } // } // } // // This package was original written to allow reporting to Bugsnag, // but after I found similar packages by Facebook and Dropbox, it // was moved to one canonical location so everyone can benefit. package errors import ( "bytes" "fmt" "reflect" "runtime" ) // The maximum number of stackframes on any error. var MaxStackDepth = 50 // Error is an error with an attached stacktrace. It can be used // wherever the builtin error interface is expected. type Error struct { Err error stack []uintptr frames []StackFrame prefix string } // New makes an Error from the given value. If that value is already an // error then it will be used directly, if not, it will be passed to // fmt.Errorf("%v"). The stacktrace will point to the line of code that // called New. func New(e interface{}) *Error { var err error switch e := e.(type) { case error: err = e default: err = fmt.Errorf("%v", e) } stack := make([]uintptr, MaxStackDepth) length := runtime.Callers(2, stack[:]) return &Error{ Err: err, stack: stack[:length], } } // Wrap makes an Error from the given value. If that value is already an // error then it will be used directly, if not, it will be passed to // fmt.Errorf("%v"). The skip parameter indicates how far up the stack // to start the stacktrace. 0 is from the current call, 1 from its caller, etc. func Wrap(e interface{}, skip int) *Error { if e == nil { return nil } var err error switch e := e.(type) { case *Error: return e case error: err = e default: err = fmt.Errorf("%v", e) } stack := make([]uintptr, MaxStackDepth) length := runtime.Callers(2+skip, stack[:]) return &Error{ Err: err, stack: stack[:length], } } // WrapPrefix makes an Error from the given value. If that value is already an // error then it will be used directly, if not, it will be passed to // fmt.Errorf("%v"). The prefix parameter is used to add a prefix to the // error message when calling Error(). The skip parameter indicates how far // up the stack to start the stacktrace. 0 is from the current call, // 1 from its caller, etc. func WrapPrefix(e interface{}, prefix string, skip int) *Error { if e == nil { return nil } err := Wrap(e, 1+skip) if err.prefix != "" { prefix = fmt.Sprintf("%s: %s", prefix, err.prefix) } return &Error{ Err: err.Err, stack: err.stack, prefix: prefix, } } // Errorf creates a new error with the given message. You can use it // as a drop-in replacement for fmt.Errorf() to provide descriptive // errors in return values. func Errorf(format string, a ...interface{}) *Error { return Wrap(fmt.Errorf(format, a...), 1) } // Error returns the underlying error's message. func (err *Error) Error() string { msg := err.Err.Error() if err.prefix != "" { msg = fmt.Sprintf("%s: %s", err.prefix, msg) } return msg } // Stack returns the callstack formatted the same way that go does // in runtime/debug.Stack() func (err *Error) Stack() []byte { buf := bytes.Buffer{} for _, frame := range err.StackFrames() { buf.WriteString(frame.String()) } return buf.Bytes() } // Callers satisfies the bugsnag ErrorWithCallerS() interface // so that the stack can be read out. func (err *Error) Callers() []uintptr { return err.stack } // ErrorStack returns a string that contains both the // error message and the callstack. func (err *Error) ErrorStack() string { return err.TypeName() + " " + err.Error() + "\n" + string(err.Stack()) } // StackFrames returns an array of frames containing information about the // stack. func (err *Error) StackFrames() []StackFrame { if err.frames == nil { err.frames = make([]StackFrame, len(err.stack)) for i, pc := range err.stack { err.frames[i] = NewStackFrame(pc) } } return err.frames } // TypeName returns the type this error. e.g. *errors.stringError. func (err *Error) TypeName() string { if _, ok := err.Err.(uncaughtPanic); ok { return "panic" } return reflect.TypeOf(err.Err).String() }