merry

package module
v2.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 14, 2024 License: MIT Imports: 9 Imported by: 80

README

merry Build GoDoc Go Report Card

Add context to errors, including automatic stack capture, cause chains, HTTP status code, user messages, and arbitrary values.

The package is largely based on http://github.com/go-errors/errors, with additional inspiration from https://github.com/go-errgo/errgo and https://github.com/amattn/deeperror.

Installation

go get github.com/ansel1/merry/v2

Features

Wrapped errors work a lot like google's golang.org/x/net/context package: each wrapper error contains the inner error, a key, and a value. Like contexts, errors are immutable: adding a key/value to an error always creates a new error which wraps the original.

This package comes with built-in support for adding information to errors:

  • stacktraces
  • changing the error message
  • HTTP status codes
  • End user error messages
  • causes

You can also add your own additional information.

The stack capturing feature can be turned off for better performance, though it's pretty fast. Benchmarks on an 2017 MacBook Pro, with go 1.10:

BenchmarkNew_withStackCapture-8      	 2000000	       749 ns/op
BenchmarkNew_withoutStackCapture-8   	20000000	        64.1 ns/op

Merry errors are fully compatible with errors.Is, As, and Unwrap.

Example

To add a stacktrace, a user message, and an HTTP code to an error:

err = merry.Wrap(err, WithUserMessagef("Username %s not found.", username), WithHTTPCode(404))

To fetch context information from error:

userMsg := UserMessage(err)
statusCode := HTTPCode(err)
stacktrace := Stacktrace(err)

To print full details of the error:

log.Printf("%v+", err)  // or log.Println(merry.Details(err))

v1 -> v2

v1 used a fluent API style which proved awkward in some cases. In general, fluent APIs don't work that well in go, because they interfere with how interfaces are typically used to compose with other packages. v2 uses a functional options API style which more easily allows other packages to augment this one.

This also fixed bad smell with v1's APIs: they mostly returned a big, ugly merry.Error interface, instead of plain error instances. v2 has a smaller, simpler API, which exclusively uses plain errors.

v2 also implements a simpler and more robust integration with errors.Is/As/Unwrap. v2's functions will work with error wrapper chains even if those chains contain errors not created with this package, so long as those errors conform to the Unwrap() convention.

v2 allows more granular control over stacktraces: stack capture can be turned on or off on individual errors, overriding the global setting. External stacktraces can be used as well.

v1 has been reimplemented in terms of v2, and the versions are completely compatible, and can be mixed.

Plugs

License

This package is licensed under the MIT license, see LICENSE.MIT for details.

Documentation

Overview

Package merry adds context to errors, including automatic stack capture, cause chains, HTTP status code, user messages, and arbitrary values.

Wrapped errors work a lot like google's golang.org/x/net/context package: each wrapper error contains the inner error, a key, and a value. Like contexts, errors are immutable: adding a key/value to an error always creates a new error which wraps the original.

This package comes with built-in support for adding information to errors:

* stacktraces * changing the error message * HTTP status codes * End user error messages * causes

You can also add your own additional information.

The stack capturing feature can be turned off for better performance, though it's pretty fast. Benchmarks on an 2017 MacBook Pro, with go 1.10:

BenchmarkNew_withStackCapture-8      	 2000000	       749 ns/op
BenchmarkNew_withoutStackCapture-8   	20000000	        64.1 ns/op

Usage

This package contains functions for creating errors, or wrapping existing errors. To create:

err := New("boom!")
err := Errorf("error fetching %s", filename)

Additional context information can be attached to errors using functional options, called Wrappers:

err := New("record not found", WithHTTPCode(404))

Errorf() also accepts wrappers, mixed in with the format args:

err := Errorf("user %s not found", username, WithHTTPCode(404))

Wrappers can be applied to existing errors with Wrap():

err = Wrap(err, WithHTTPCode(404))

Wrap() will add a stacktrace to any error which doesn't already have one attached. WrapSkipping() can be used to control where the stacktrace starts.

This package contains wrappers for adding specific context information to errors, such as an HTTPCode. You can create your own wrappers using the primitive Value(), WithValue(), and Set() functions.

Errors produced by this package implement fmt.Formatter, to print additional information about the error:

fmt.Printf("%v", err)         // print error message and causes
fmt.Printf("%s", err)         // same as %s
fmt.Printf("%q", err)         // same as fmt.Printf("%q", err.Error())
fmt.Printf("%v+", err)        // print Details(err)

Details() prints the error message, all causes, the stacktrace, and additional error values configured with RegisterDetailFunc(). By default, it will show the HTTP status code and user message.

Stacktraces

By default, any error created by or wrapped by this package will automatically have a stacktrace captured and attached to the error. This capture only happens if the error doesn't already have a stack attached to it, so wrapping the error with additional context won't capture additional stacks.

When and how stacks are captured can be customized. SetMaxStackDepth() can globally configure how many frames to capture. SetStackCaptureEnabled() can globally configure whether stacks are captured by default.

Wrap(err, NoStackCapture()) can be used to selectively suppress stack capture for a particular error.

Wrap(err, CaptureStack(false)) will capture a new stack at the Wrap call site, even if the err already had an earlier stack attached. The new stack overrides the older stack.

Wrap(err, CaptureStack(true)) will force a stack capture at the call site even if stack capture is disabled globally.

Finally, Wrappers are passed a depth argument so they know how deep they are in the call stack from the call site where this package's API was called. This allows Wrappers to implement their own stack capturing logic.

The package contains functions for creating new errors with stacks, or adding a stack to `error` instances. Functions with add context (e.g. `WithValue()`) work on any `error`, and will automatically convert them to merry errors (with a stack) if necessary.

Hooks

AddHooks() can install wrappers which are applied to all errors processed by this package. Hooks are applied before any other wrappers or processing takes place. They can be used to integrate with errors from other packages, normalizing errors (such as applying standard status codes to application errors), localizing user messages, or replacing the stack capturing mechanism.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddHooks

func AddHooks(hook ...Wrapper)

AddHooks installs a global set of Wrappers which are applied to every error processed by this package. They are applied before any other Wrappers or stack capturing are applied. Hooks can add additional wrappers to errors, or translate annotations added by other error libraries into merry annotations.

Note that these hooks will be applied each time an err is passed to Wrap/Apply. If you only want your hook to run once per error, see AddOnceHooks.

This function is not thread safe, and should only be called very early in program initialization.

func AddOnceHooks

func AddOnceHooks(hook ...Wrapper)

AddOnceHooks is like AddHooks, but these hooks will only be applied once per error. Once hooks are applied to an error, the error is marked, and future Wrap/Apply calls on the error will not apply these hooks again.

This function is not thread safe, and should only be called very early in program initialization.

func Append

func Append(err error, msg string, wrappers ...Wrapper) error

Append is a convenience function for the AppendMessage wrapper. It eases migration from merry v1. It accepts a varargs of additional Wrappers.

func Appendf

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

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 Apply

func Apply(err error, wrappers ...Wrapper) error

Apply is like Wrap, but does not execute hooks or do automatic stack capture. It just applies the wrappers to the error.

func ApplySkipping

func ApplySkipping(err error, skip int, wrappers ...Wrapper) error

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 Cause

func Cause(err error) error

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

func ClearHooks

func ClearHooks()

ClearHooks removes all installed hooks.

This function is not thread safe, and should only be called very early in program initialization.

func Details

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 Errorf

func Errorf(format string, args ...interface{}) error

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 Format

func Format(s fmt.State, verb rune, err error)

Format adapts errors to fmt.Formatter interface. It's intended to be used help error impls implement fmt.Formatter, e.g.:

    func (e *myErr) Format(f fmt.State, verb rune) {
	     Format(f, verb, e)
    }

func FormattedStack

func FormattedStack(err error) []string

FormattedStack returns the stack attached to an error, formatted as a slice of strings. Each string represents a frame in the stack, newest first. The strings may have internal newlines.

Returns nil if no formatted stack and no stack is associated, or err is nil.

func HTTPCode

func HTTPCode(err 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 HasStack

func HasStack(err error) bool

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 Location

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

Location returns zero values if e has no stacktrace

func Lookup

func Lookup(err error, key interface{}) (interface{}, bool)

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 MaxStackDepth

func MaxStackDepth() int

MaxStackDepth returns the number of frames captured in stacks.

func New

func New(msg string, wrappers ...Wrapper) error

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

func Prepend

func Prepend(err error, msg string, wrappers ...Wrapper) error

Prepend is a convenience function for the PrependMessage wrapper. It eases migration from merry v1. It accepts a varargs of additional Wrappers.

func Prependf

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

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 RegisterDetail

func RegisterDetail(label string, key interface{})

RegisterDetail registers an error property key in a global registry, with a label. See RegisterDetailFunc. This function just wraps a call to Value(key) and passes it to RegisterDetailFunc.

func RegisterDetailFunc

func RegisterDetailFunc(label string, f func(err error) interface{})

RegisterDetailFunc registers a label and a function for extracting a value from an error. When formatting errors produced by this package using the `%+v` placeholder, or when using Details(), these functions will be called on the error, and any non-nil values will be added to the text. For example:

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

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

func Color(err) string {
  s, _ := Value(err, colorKey)
  return s
}

RegisterDetailFunc("color", Color)
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 RegisteredDetails

func RegisteredDetails(err error) map[string]interface{}

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 Sentinel

func Sentinel(msg string, wrappers ...Wrapper) error

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 Sentinelf

func Sentinelf(format string, args ...interface{}) error

Sentinelf is like Sentinel, but takes a formatted message. args can be a mix of format arguments and Wrappers.

func Set

func Set(err error, key, value interface{}) error

Set wraps an error with a key/value pair. This is the simplest form of associating a value with an error. It does not capture a stacktrace, invoke hooks, or do any other processing. It is mainly intended as a primitive for writing Wrapper implementations.

if err is nil, returns nil.

func SetMaxStackDepth

func SetMaxStackDepth(depth int)

SetMaxStackDepth sets the MaxStackDepth.

func SetStackCaptureEnabled

func SetStackCaptureEnabled(enabled bool)

SetStackCaptureEnabled sets stack capturing globally. Disabling stack capture can increase performance. Capture can be forced or suppressed to override this global setting on a particular error.

func SourceLine

func SourceLine(err error) string

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

func Stack

func Stack(err 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

func StackCaptureEnabled() bool

StackCaptureEnabled returns whether stack capturing is enabled.

func Stacktrace

func Stacktrace(err error) string

Stacktrace returns the error's stacktrace as a string formatted. If e has no stacktrace, returns an empty string.

func UserMessage

func UserMessage(err error) string

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

func Value

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

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

func Values

func Values(err 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 Wrap

func Wrap(err error, wrappers ...Wrapper) error

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 WrapSkipping

func WrapSkipping(err error, skip int, wrappers ...Wrapper) error

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.

Types

type Wrapper

type Wrapper interface {
	// Wrap returns a new error, wrapping the argument, and typically adding some context information.
	// skipCallers is how many callers to skip when capturing a stack to skip to the caller of the merry
	// API surface.  It's intended to make it possible to write wrappers which capture stacktraces.  e.g.
	//
	//     func CaptureStack() Wrapper {
	//         return WrapperFunc(func(err error, skipCallers int) error {
	//             s := make([]uintptr, 50)
	//             // Callers
	//             l := runtime.Callers(2+skipCallers, s[:])
	//             return WithStack(s[:l]).Wrap(err, skipCallers + 1)
	//         })
	//    }
	Wrap(err error, skipCallers int) error
}

Wrapper knows how to wrap errors with context information.

func AppendMessage

func AppendMessage(msg string) Wrapper

AppendMessage a message after the current error message, in the format "original: new".

func AppendMessagef

func AppendMessagef(format string, args ...interface{}) Wrapper

AppendMessagef is the same as AppendMessage, but with a formatted message.

func CaptureStack

func CaptureStack(force bool) Wrapper

CaptureStack will override an earlier stack with a stack captured from the current call site. If StackCaptureEnabled() == false, this is a no-op.

If force is set, StackCaptureEnabled() will be ignored: a stack will always be captured.

func NoCaptureStack

func NoCaptureStack() Wrapper

NoCaptureStack will suppress capturing a stack, even if StackCaptureEnabled() == true.

func PrependMessage

func PrependMessage(msg string) Wrapper

PrependMessage a message before the current error message, in the format "new: original".

func PrependMessagef

func PrependMessagef(format string, args ...interface{}) Wrapper

PrependMessagef is the same as PrependMessage, but with a formatted message.

func WithCause

func WithCause(err error) Wrapper

WithCause sets one error as the cause of another error. This is useful for associating errors from lower API levels with sentinel errors in higher API levels. errors.Is() and errors.As() will traverse both the main chain of error wrappers, and down the chain of causes.

If err is nil, this is a no-op

func WithFormattedStack

func WithFormattedStack(stack []string) Wrapper

WithFormattedStack associates a stack of pre-formatted strings describing frames of a stacktrace. Generally, a formatted stack is generated from the raw []uintptr stack associated with the error, but a pre-formatted stack can be associated with the error instead, and takes precedence over the raw stack. This is useful if pre-formatted stack information is coming from some other source.

func WithHTTPCode

func WithHTTPCode(statusCode int) Wrapper

WithHTTPCode associates an HTTP status code with an error.

func WithMessage

func WithMessage(msg string) Wrapper

WithMessage overrides the value returned by err.Error().

func WithMessagef

func WithMessagef(format string, args ...interface{}) Wrapper

WithMessagef overrides the value returned by err.Error().

func WithStack

func WithStack(stack []uintptr) Wrapper

WithStack associates a stack of caller frames with an error. Generally, this package will automatically capture and associate a stack with errors which are created or wrapped by this package. But this allows the caller to associate an externally generated stack.

func WithUserMessage

func WithUserMessage(msg string) Wrapper

WithUserMessage associates an end-user message with an error.

func WithUserMessagef

func WithUserMessagef(format string, args ...interface{}) Wrapper

WithUserMessagef associates a formatted end-user message with an error.

func WithValue

func WithValue(key, value interface{}) Wrapper

WithValue associates a key/value pair with an error.

type WrapperFunc

type WrapperFunc func(error, int) error

WrapperFunc implements Wrapper.

func (WrapperFunc) Wrap

func (w WrapperFunc) Wrap(err error, callerDepth int) error

Wrap implements the Wrapper interface.

Directories

Path Synopsis
Package status is a drop-in replacement for the google.golang.org/grpc/status package, but is compatible with merry errors.
Package status is a drop-in replacement for the google.golang.org/grpc/status package, but is compatible with merry errors.
Package pkgerrors provides a merry hook to integrate pkg/errors stacktraces with merry.
Package pkgerrors provides a merry hook to integrate pkg/errors stacktraces with merry.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL