merry: github.com/ansel1/merry Index | Files

package merry

import "github.com/ansel1/merry"

Package merry provides enriched golang errors, with stacktraces

merry creates errors with stacktraces, and can augment those errors with additional information.

When you create a new merry error, or wrap an existing error in a merry error, merry attaches a stacktrace to the error:

err := merry.New("an error occurred")

err has a stacktrace attached. Alternately, you can wrap existing errors. merry will attach a stacktrace at the point of wrapping:

_, err := ioutil.ReadAll(r)
if err != nil {
    return merry.Wrap(err)
}

Capturing the stack can be globally disabled with `SetStackCaptureEnabled(false)`. Wrapping is idempotent: Wrap will only attach a stacktrace if the error doesn't already have one.

Wrap() is the simplest way to attach a stacktrace to an error, but other functions can be used instead, with both add a stacktrace, and augment or modify the error. For example, Prepend() modifies the error's message (and also attaches a stacktrace):

_, err := ioutil.ReadAll(r)
if err != nil {
    return merry.Prepend(err, "reading from conn failed")
    // err.Error() would read something like "reading from conn failed: timeout"
}

See the other package functions for other ways to augment or modify errors, such as Append, WithUserMessage, WithHTTPCode, WithValue, etc. These functions all return a merry.Error interface, which has methods which mirror the package level functions, to allow simple chaining:

return merry.New("object not found").WithHTTPCode(404)

Here

Wrap will not take a new stacktrace if an error already has one attached. Here will create a new error which replaces the stacktrace with a new one:

var ErrOverflow = merry.New("overflowed")

func Read() error {
    // ...
    return merry.Here(ErrOverflow)
}

Is

The go idiom of exporting package-level error variables for comparison to errors returned by the package is broken by merry. For example:

_, err := io.ReadAll(r)
if err == io.EOF {
    // ...
}

If the error returned was a merry error, the equality comparison would always fail, because merry augments errors by wrapping them in layers. To compensate for this, merry has the Is() function.

if merry.Is(err, io.EOF) {

Is() will unwrap the err and compare each layer to the second argument.

Cause

You can add a cause to an error:

if err == io.EOF {
    err = merry.New("reading failed"), err)
    fmt.Println(err.Error()) // reading failed: EOF
}

Cause(error) will return the cause of the argument. RootCause(error) returns the innermost cause. Is(err1, err2) is cause aware, and will return true if err2 is a cause (anywhere in the causal change) of err1.

Formatting and printing

To obtain an error's stacktrace, call Stack(). To get other information about the site of the error, or print the error's stacktrace, see Location(), SourceLine(), Stacktrace(), and Details().

merry errors also implement the fmt.Formatter interface. errors support the following fmt flags:

%+v   print the equivalent of Details(err), which includes the user message, full stacktrace,
      and recursively prints the details of the cause chain.

Index

Package Files

doc.go errors.go print.go

Variables

var MaxStackDepth = 50

MaxStackDepth is the maximum number of stackframes on any error.

func Cause Uses

func Cause(e error) error

Cause returns the cause of the argument. If e is nil, or has no cause, nil is returned.

func Details Uses

func Details(e error) string

Details returns e.Error(), e's stacktrace, and any additional details which have be registered with RegisterDetail. User message and HTTP code are already registered.

The details of each error in e's cause chain will also be printed.

func HTTPCode Uses

func HTTPCode(e error) int

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 Is Uses

func Is(e error, originals ...error) bool

Is checks whether e is equal to or wraps the original, at any depth. If e == nil, return false. This is useful if your package uses the common golang pattern of exported error constants. If your package exports an ErrEOF constant, which is initialized like this:

var ErrEOF = errors.New("End of file error")

...and your user wants to compare an error returned by your package with ErrEOF:

err := urpack.Read()
if err == urpack.ErrEOF {

...the comparison will fail if the error has been wrapped by merry at some point. Replace the comparison with:

if merry.Is(err, urpack.ErrEOF) {

Causes

Is will also return true if any of the originals is in the cause chain of e. For example:

e1 := merry.New("base error")
e2 := merry.New("library error")
// e2 was caused by e1
e3 := merry.WithCause(e1, e2)
merry.Is(e3, e2)  // yes it is, because e3 is based on e2
merry.Is(e3, e1)  // yes it is, because e1 was a cause of e3

func Location Uses

func Location(e error) (file string, line int)

Location returns zero values if e has no stacktrace

func Message Uses

func Message(e error) string

Message returns just the error message. It is equivalent to Error() when Verbose is false. The behavior of Error() is (pseudo-code):

if verbose
  Details(e)
else
  Message(e) || UserMessage(e)

If e is nil, returns "".

func RegisterDetail Uses

func RegisterDetail(label string, key interface{})

RegisterDetail registers an error property key in a global registry, with a label. The registry is used by the Details() function. Registered error properties will be included in Details() output, if the value of that error property is not nil. For example:

err := New("boom")
err = err.WithValue(colorKey, "red")
fmt.Println(Details(err))

// Output:
// boom
//
// <stacktrace>

RegisterDetail("Color", colorKey)
fmt.Println(Details(err))

// Output:
// boom
// Color: red
//
// <stacktrace>

Error property keys are typically not exported by the packages which define them. Packages instead export functions which let callers access that property. It's therefore up to the package to register those properties which would make sense to include in the Details() output. In other words, it's up to the author of the package which generates the errors to publish printable error details, not the callers of the package.

func RootCause Uses

func RootCause(e error) error

RootCause returns the innermost cause of the argument (i.e. the last error in the cause chain)

func SetStackCaptureEnabled Uses

func SetStackCaptureEnabled(enabled bool)

SetStackCaptureEnabled sets stack capturing globally. Disabling stack capture can increase performance

func SetVerboseDefault Uses

func SetVerboseDefault(b bool)

SetVerboseDefault sets the global default for verbose mode. When true, e.Error() == Details(e) When false, e.Error() == Message(e) + Cause(e)

func SourceLine Uses

func SourceLine(e error) string

SourceLine returns the string representation of Location's result or an empty string if there's no stracktrace.

func Stack Uses

func Stack(e error) []uintptr

Stack returns the stack attached to an error, or nil if one is not attached If e is nil, returns nil.

func StackCaptureEnabled Uses

func StackCaptureEnabled() bool

StackCaptureEnabled returns whether stack capturing is enabled

func Stacktrace Uses

func Stacktrace(e error) string

Stacktrace returns the error's stacktrace as a string formatted the same way as golangs runtime package. If e has no stacktrace, returns an empty string.

func Unwrap Uses

func Unwrap(e error) error

Unwrap returns the innermost underlying error. Only useful in advanced cases, like if you need to cast the underlying error to some type to get additional information from it. If e == nil, return nil.

func UserMessage Uses

func UserMessage(e error) string

UserMessage returns the end-user safe message. Returns empty if not set. If e is nil, returns "".

func Value Uses

func Value(e error, key interface{}) interface{}

Value returns the value for key, or nil if not set. If e is nil, returns nil.

func Values Uses

func Values(e error) map[interface{}]interface{}

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 VerboseDefault Uses

func VerboseDefault() bool

VerboseDefault returns the global default for verbose mode. When true, e.Error() == Details(e) When false, e.Error() == Message(e) + Cause(e)

type Error Uses

type Error interface {
    error
    Appendf(format string, args ...interface{}) Error
    Append(msg string) Error
    Prepend(msg string) Error
    Prependf(format string, args ...interface{}) Error
    WithMessage(msg string) Error
    WithMessagef(format string, args ...interface{}) Error
    WithUserMessage(msg string) Error
    WithUserMessagef(format string, args ...interface{}) Error
    WithValue(key, value interface{}) Error
    Here() Error
    WithStackSkipping(skip int) Error
    WithHTTPCode(code int) Error
    WithCause(err error) Error
    Cause() error
    fmt.Formatter
}

Error extends the standard golang `error` interface with functions for attachment additional data to the error

func Append Uses

func Append(e error, msg string) Error

Append a message after the current error message, in the format "original: new". If e == nil, return nil.

func Appendf Uses

func Appendf(e error, format string, args ...interface{}) Error

Appendf is the same as Append, but uses fmt.Sprintf().

func Errorf Uses

func Errorf(format string, a ...interface{}) Error

Errorf creates a new error with a formatted message and a stack. The equivalent of golang's fmt.Errorf()

func Here Uses

func Here(e error) Error

Here returns an error with a new stacktrace, at the call site of Here(). Useful when returning copies of exported package errors. If e is nil, returns nil.

func HereSkipping Uses

func HereSkipping(e error, skip int) Error

HereSkipping returns an error with a new stacktrace, at the call site of HereSkipping() - skip frames.

func New Uses

func New(msg string) Error

New creates a new error, with a stack attached. The equivalent of golang's errors.New()

func Prepend Uses

func Prepend(e error, msg string) Error

Prepend a message before the current error message, in the format "new: original". If e == nil, return nil.

func Prependf Uses

func Prependf(e error, format string, args ...interface{}) Error

Prependf is the same as Prepend, but uses fmt.Sprintf()

func UserError Uses

func UserError(msg string) Error

UserError creates a new error with a message intended for display to an end user.

func UserErrorf Uses

func UserErrorf(format string, a ...interface{}) Error

UserErrorf is like UserError, but uses fmt.Sprintf()

func WithCause Uses

func WithCause(e error, cause error) Error

WithCause returns an error based on the first argument, with the cause set to the second argument. If e is nil, returns nil.

func WithHTTPCode Uses

func WithHTTPCode(e error, code int) Error

WithHTTPCode returns an error with an http code attached. If e is nil, returns nil.

func WithMessage Uses

func WithMessage(e error, msg string) Error

WithMessage returns an error with a new message. The resulting error's Error() method will return the new message. If e is nil, returns nil.

func WithMessagef Uses

func WithMessagef(e error, format string, a ...interface{}) Error

WithMessagef is the same as WithMessage(), using fmt.Sprintf().

func WithUserMessage Uses

func WithUserMessage(e error, msg string) Error

WithUserMessage adds a message which is suitable for end users to see. If e is nil, returns nil.

func WithUserMessagef Uses

func WithUserMessagef(e error, format string, args ...interface{}) Error

WithUserMessagef is the same as WithMessage(), using fmt.Sprintf()

func WithValue Uses

func WithValue(e error, key, value interface{}) Error

WithValue adds a context an error. If the key was already set on e, the new value will take precedence. If e is nil, returns nil.

func Wrap Uses

func Wrap(e error) Error

Wrap turns the argument into a merry.Error. If the argument already is a merry.Error, this is a no-op. If e == nil, return nil

func WrapSkipping Uses

func WrapSkipping(e error, skip int) Error

WrapSkipping turns the error arg into a merry.Error if the arg is not already a merry.Error. If e is nil, return nil. If a merry.Error is created by this call, the stack captured will skip `skip` frames (0 is the call site of `WrapSkipping()`)

Package merry imports 8 packages (graph) and is imported by 26 packages. Updated 2018-12-09. Refresh now. Tools for package owners.