mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-11-23 06:40:23 +00:00
335 lines
10 KiB
Go
335 lines
10 KiB
Go
|
package merry
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"runtime"
|
||
|
)
|
||
|
|
||
|
// New creates a new error, with a stack attached. The equivalent of golang's errors.New()
|
||
|
func New(msg string, wrappers ...Wrapper) error {
|
||
|
return WrapSkipping(errors.New(msg), 1, wrappers...)
|
||
|
}
|
||
|
|
||
|
// Errorf creates a new error with a formatted message and a stack. The equivalent of golang's fmt.Errorf().
|
||
|
// args may contain either arguments to format, or Wrapper options, which will be applied to the error.
|
||
|
func Errorf(format string, args ...interface{}) error {
|
||
|
fmtArgs, wrappers := splitWrappers(args)
|
||
|
|
||
|
return WrapSkipping(fmt.Errorf(format, fmtArgs...), 1, wrappers...)
|
||
|
}
|
||
|
|
||
|
// Sentinel creates an error without running hooks or capturing a stack. It is intended
|
||
|
// to create sentinel errors, which will be wrapped with a stack later from where the
|
||
|
// error is returned. At that time, a stack will be captured and hooks will be run.
|
||
|
//
|
||
|
// var ErrNotFound = merry.Sentinel("not found", merry.WithHTTPCode(404))
|
||
|
//
|
||
|
// func FindUser(name string) (*User, error) {
|
||
|
// // some db code which fails to find a user
|
||
|
// return nil, merry.Wrap(ErrNotFound)
|
||
|
// }
|
||
|
//
|
||
|
// func main() {
|
||
|
// _, err := FindUser("bob")
|
||
|
// fmt.Println(errors.Is(err, ErrNotFound) // "true"
|
||
|
// fmt.Println(merry.Details(err)) // stacktrace will start at the return statement
|
||
|
// // in FindUser()
|
||
|
// }
|
||
|
func Sentinel(msg string, wrappers ...Wrapper) error {
|
||
|
return ApplySkipping(errors.New(msg), 1, wrappers...)
|
||
|
}
|
||
|
|
||
|
// Sentinelf is like Sentinel, but takes a formatted message. args can be a mix of
|
||
|
// format arguments and Wrappers.
|
||
|
func Sentinelf(format string, args ...interface{}) error {
|
||
|
fmtArgs, wrappers := splitWrappers(args)
|
||
|
|
||
|
return ApplySkipping(fmt.Errorf(format, fmtArgs...), 1, wrappers...)
|
||
|
}
|
||
|
|
||
|
func splitWrappers(args []interface{}) ([]interface{}, []Wrapper) {
|
||
|
var wrappers []Wrapper
|
||
|
|
||
|
// pull out the args which are wrappers
|
||
|
n := 0
|
||
|
for _, arg := range args {
|
||
|
if w, ok := arg.(Wrapper); ok {
|
||
|
wrappers = append(wrappers, w)
|
||
|
} else {
|
||
|
args[n] = arg
|
||
|
n++
|
||
|
}
|
||
|
}
|
||
|
args = args[:n]
|
||
|
|
||
|
return args, wrappers
|
||
|
}
|
||
|
|
||
|
// Wrap adds context to errors by applying Wrappers. See WithXXX() functions for Wrappers supplied
|
||
|
// by this package.
|
||
|
//
|
||
|
// If StackCaptureEnabled is true, a stack starting at the caller will be automatically captured
|
||
|
// and attached to the error. This behavior can be overridden with wrappers which either capture
|
||
|
// their own stacks, or suppress auto capture.
|
||
|
//
|
||
|
// If err is nil, returns nil.
|
||
|
func Wrap(err error, wrappers ...Wrapper) error {
|
||
|
return WrapSkipping(err, 1, wrappers...)
|
||
|
}
|
||
|
|
||
|
// WrapSkipping is like Wrap, but the captured stacks will start `skip` frames
|
||
|
// further up the call stack. If skip is 0, it behaves the same as Wrap.
|
||
|
func WrapSkipping(err error, skip int, wrappers ...Wrapper) error {
|
||
|
if err == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if len(onceHooks) > 0 {
|
||
|
if _, ok := Lookup(err, errKeyHooked); !ok {
|
||
|
err = ApplySkipping(err, skip+1, onceHooks...)
|
||
|
err = ApplySkipping(err, skip+1, WithValue(errKeyHooked, err))
|
||
|
}
|
||
|
}
|
||
|
err = ApplySkipping(err, skip+1, hooks...)
|
||
|
err = ApplySkipping(err, skip+1, wrappers...)
|
||
|
return captureStack(err, skip+1, false)
|
||
|
}
|
||
|
|
||
|
// Apply is like Wrap, but does not execute hooks or do automatic stack capture. It just
|
||
|
// applies the wrappers to the error.
|
||
|
func Apply(err error, wrappers ...Wrapper) error {
|
||
|
return ApplySkipping(err, 1, wrappers...)
|
||
|
}
|
||
|
|
||
|
// ApplySkipping is like WrapSkipping, but does not execute hooks or do automatic stack capture. It just
|
||
|
// applies the wrappers to the error. It is useful in Wrapper implementations which
|
||
|
// // want to apply other Wrappers without starting an infinite recursion.
|
||
|
func ApplySkipping(err error, skip int, wrappers ...Wrapper) error {
|
||
|
if err == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
for _, w := range wrappers {
|
||
|
err = w.Wrap(err, skip+1)
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Prepend is a convenience function for the PrependMessage wrapper. It eases migration
|
||
|
// from merry v1. It accepts a varargs of additional Wrappers.
|
||
|
func Prepend(err error, msg string, wrappers ...Wrapper) error {
|
||
|
return WrapSkipping(err, 1, append(wrappers, PrependMessage(msg))...)
|
||
|
}
|
||
|
|
||
|
// Prependf is a convenience function for the PrependMessagef wrapper. It eases migration
|
||
|
// from merry v1. The args can be format arguments mixed with Wrappers.
|
||
|
func Prependf(err error, format string, args ...interface{}) error {
|
||
|
fmtArgs, wrappers := splitWrappers(args)
|
||
|
|
||
|
return WrapSkipping(err, 1, append(wrappers, PrependMessagef(format, fmtArgs...))...)
|
||
|
}
|
||
|
|
||
|
// Append is a convenience function for the AppendMessage wrapper. It eases migration
|
||
|
// from merry v1. It accepts a varargs of additional Wrappers.
|
||
|
func Append(err error, msg string, wrappers ...Wrapper) error {
|
||
|
return WrapSkipping(err, 1, append(wrappers, AppendMessage(msg))...)
|
||
|
}
|
||
|
|
||
|
// Appendf is a convenience function for the AppendMessagef wrapper. It eases migration
|
||
|
// from merry v1. The args can be format arguments mixed with Wrappers.
|
||
|
func Appendf(err error, format string, args ...interface{}) error {
|
||
|
fmtArgs, wrappers := splitWrappers(args)
|
||
|
|
||
|
return WrapSkipping(err, 1, append(wrappers, AppendMessagef(format, fmtArgs...))...)
|
||
|
}
|
||
|
|
||
|
// Value returns the value for key, or nil if not set.
|
||
|
// If e is nil, returns nil. Will not search causes.
|
||
|
func Value(err error, key interface{}) interface{} {
|
||
|
v, _ := Lookup(err, key)
|
||
|
return v
|
||
|
}
|
||
|
|
||
|
// Lookup returns the value for the key, and a boolean indicating
|
||
|
// whether the value was set. Will not search causes.
|
||
|
//
|
||
|
// if err is nil, returns nil and false.
|
||
|
func Lookup(err error, key interface{}) (interface{}, bool) {
|
||
|
var merr interface {
|
||
|
error
|
||
|
isMerryError()
|
||
|
}
|
||
|
|
||
|
// I've tried implementing this logic a few different ways. It's tricky:
|
||
|
//
|
||
|
// - Lookup should only search the current error, but not causes. errWithCause's
|
||
|
// Unwrap() will eventually unwrap to the cause, so we don't want to just
|
||
|
// search the entire stream of errors returned by Unwrap.
|
||
|
// - We need to handle cases where error implementations created outside
|
||
|
// this package are in the middle of the chain. We need to use Unwrap
|
||
|
// in these cases to traverse those errors and dig down to the next
|
||
|
// merry error.
|
||
|
// - Some error packages, including our own, do funky stuff with Unwrap(),
|
||
|
// returning shim types to control the unwrapping order, rather than
|
||
|
// the actual, raw wrapped error. Typically, these shims implement
|
||
|
// Is/As to delegate to the raw error they encapsulate, but implement
|
||
|
// Unwrap by encapsulating the raw error in another shim. So if we're looking
|
||
|
// for a raw error type, we can't just use Unwrap() and do type assertions
|
||
|
// against the result. We have to use errors.As(), to allow the shims to delegate
|
||
|
// the type assertion to the raw error correctly.
|
||
|
//
|
||
|
// Based on all these constraints, we use errors.As() with an internal interface
|
||
|
// that can only be implemented by our internal error types. When one is found,
|
||
|
// we handle each of our internal types as a special case. For errWithCause, we
|
||
|
// traverse to the wrapped error, ignoring the cause and the funky Unwrap logic.
|
||
|
// We could have just used errors.As(err, *errWithValue), but that would have
|
||
|
// traversed into the causes.
|
||
|
|
||
|
for {
|
||
|
switch t := err.(type) {
|
||
|
case *errWithValue:
|
||
|
if t.key == key {
|
||
|
return t.value, true
|
||
|
}
|
||
|
err = t.err
|
||
|
case *errWithCause:
|
||
|
err = t.err
|
||
|
default:
|
||
|
if errors.As(err, &merr) {
|
||
|
err = merr
|
||
|
} else {
|
||
|
return nil, false
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Values returns a map of all values attached to the error
|
||
|
// If a key has been attached multiple times, the map will
|
||
|
// contain the last value mapped
|
||
|
// If e is nil, returns nil.
|
||
|
func Values(err error) map[interface{}]interface{} {
|
||
|
var values map[interface{}]interface{}
|
||
|
|
||
|
for err != nil {
|
||
|
if e, ok := err.(*errWithValue); ok {
|
||
|
if _, ok := values[e.key]; !ok {
|
||
|
if values == nil {
|
||
|
values = map[interface{}]interface{}{}
|
||
|
}
|
||
|
values[e.key] = e.value
|
||
|
}
|
||
|
}
|
||
|
err = errors.Unwrap(err)
|
||
|
}
|
||
|
|
||
|
return values
|
||
|
}
|
||
|
|
||
|
// Stack returns the stack attached to an error, or nil if one is not attached
|
||
|
// If e is nil, returns nil.
|
||
|
func Stack(err error) []uintptr {
|
||
|
stack, _ := Value(err, errKeyStack).([]uintptr)
|
||
|
return stack
|
||
|
}
|
||
|
|
||
|
// HTTPCode converts an error to an http status code. All errors
|
||
|
// map to 500, unless the error has an http code attached.
|
||
|
// If e is nil, returns 200.
|
||
|
func HTTPCode(err error) int {
|
||
|
if err == nil {
|
||
|
return 200
|
||
|
}
|
||
|
|
||
|
code, _ := Value(err, errKeyHTTPCode).(int)
|
||
|
if code == 0 {
|
||
|
return 500
|
||
|
}
|
||
|
|
||
|
return code
|
||
|
}
|
||
|
|
||
|
// UserMessage returns the end-user safe message. Returns empty if not set.
|
||
|
// If e is nil, returns "".
|
||
|
func UserMessage(err error) string {
|
||
|
msg, _ := Value(err, errKeyUserMessage).(string)
|
||
|
return msg
|
||
|
}
|
||
|
|
||
|
// Cause returns the cause of the argument. If e is nil, or has no cause,
|
||
|
// nil is returned.
|
||
|
func Cause(err error) error {
|
||
|
var causer *errWithCause
|
||
|
if errors.As(err, &causer) {
|
||
|
return causer.cause
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// RegisteredDetails extracts details registered with RegisterDetailFunc from an error, and
|
||
|
// returns them as a map. Values may be nil.
|
||
|
//
|
||
|
// If err is nil or there are no registered details, nil is returned.
|
||
|
func RegisteredDetails(err error) map[string]interface{} {
|
||
|
detailsLock.Lock()
|
||
|
defer detailsLock.Unlock()
|
||
|
|
||
|
if len(detailFields) == 0 || err == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
dets := map[string]interface{}{}
|
||
|
|
||
|
for label, f := range detailFields {
|
||
|
dets[label] = f(err)
|
||
|
}
|
||
|
|
||
|
return dets
|
||
|
}
|
||
|
|
||
|
// captureStack: return an error with a stack attached. Stack will skip
|
||
|
// specified frames. skip = 0 will start at caller.
|
||
|
// If the err already has a stack, to auto-stack-capture is disabled globally,
|
||
|
// this is a no-op. Use force to override and force a stack capture
|
||
|
// in all cases.
|
||
|
func captureStack(err error, skip int, force bool) error {
|
||
|
if err == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
var c interface {
|
||
|
Callers() []uintptr
|
||
|
}
|
||
|
|
||
|
switch {
|
||
|
case force:
|
||
|
// always capture
|
||
|
case HasStack(err):
|
||
|
return err
|
||
|
case errors.As(err, &c):
|
||
|
// if the go-errors already captured a stack
|
||
|
// reuse it
|
||
|
if stack := c.Callers(); len(stack) > 0 {
|
||
|
return Set(err, errKeyStack, stack)
|
||
|
}
|
||
|
case !captureStacks:
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
s := make([]uintptr, MaxStackDepth())
|
||
|
length := runtime.Callers(2+skip, s[:])
|
||
|
return Set(err, errKeyStack, s[:length])
|
||
|
}
|
||
|
|
||
|
// HasStack returns true if a stack is already attached to the err.
|
||
|
// If err == nil, returns false.
|
||
|
//
|
||
|
// If a stack capture was suppressed with NoCaptureStack(), this will
|
||
|
// still return true, indicating that stack capture processing has already
|
||
|
// occurred on this error.
|
||
|
func HasStack(err error) bool {
|
||
|
_, ok := Lookup(err, errKeyStack)
|
||
|
return ok
|
||
|
}
|