218 lines
5.2 KiB
Go
218 lines
5.2 KiB
Go
// 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 {
|
|
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 {
|
|
|
|
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,
|
|
}
|
|
|
|
}
|
|
|
|
// Is detects whether the error is equal to a given error. Errors
|
|
// are considered equal by this function if they are the same object,
|
|
// or if they both contain the same error inside an errors.Error.
|
|
func Is(e error, original error) bool {
|
|
|
|
if e == original {
|
|
return true
|
|
}
|
|
|
|
if e, ok := e.(*Error); ok {
|
|
return Is(e.Err, original)
|
|
}
|
|
|
|
if original, ok := original.(*Error); ok {
|
|
return Is(e, original.Err)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// 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()
|
|
}
|