errors

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: May 5, 2018 License: BSD-2-Clause, MIT Imports: 9 Imported by: 18

README

errors-go CircleCI Go Report Card GoDoc

Motivations

Error management in Go is very flexible, the builtin error interface only require the error value to expose an Error() string method which gives a human-readable representation of what went wrong. A typical way to report errors is through the use of constant values that a program can compare error values returned by functions against to determine the issue (this is the model used by io.EOF for example).

However, this is not very flexible and creates a hard dependency between the programs and the packages they use. If a package's implementation changes the error value returned under some circumstances, or adds new errors, the programs depending on that package may need to be updated to adapt to the package's new behavior.

One approach that spread across the standard library to loosen the dependency between the package producing the errors and the program handling them has been to establish convention on interfaces that the actual error type implements. This allows the program to ask questions about the error value it received, like whether or not it's a temporary error, or if it happened because of a timeout (because the error type exposes Temporary() bool and Timeout() bool methods). This approach offers strong decoupling between components of a program and is one of the pillar concepts that this package is built upon.

Another limitation of carrying a single error value is that it doesn't allow layers of abstractions to add and carry context about the consequences of an error. When io.ErrUnexpectedEOF is returned by a low-level I/O routine, the next abstraction layer can either propagate the error, or return a different error value, the former gives no information about the consequence of the error, the latter loses information of what the original error was.

Packages like github.com/pkg/errors attempt to provide more powerful tools to compose errors and aggregate context about the cause and consequences of those errors, but they are by choice of their authors narrowed to solving a single aspect of error management.

This is where the errors-go package comes into play. It is built to be a drop-in replacement for pkg/errors while offering a wider set of error management tools that we wished we had countless times in order to build software that is more robust, expressive, and maintainable.

Types

In the errors-go package, errors can carry types, and a program can dynamically test what types an error value has. Error types are methods that take no arguments and return a boolean value. This is the same mechanism used in the standard library to test whether errors are temporary, or if they happened because of a timeout.

For example, this error may be of type Temporary, Timeout, and Unreachable

type myError struct {
    unreachable bool
    timeout     bool
}

func (e *myError) Error()       string { return "..." }
func (e *myError) Temporary()   bool   { return true }
func (e *myError) Timeout()     bool   { return e.timeout }
func (e *myError) Unreachable() bool   { return e.unreachable }

and a program may use the errors.Is(typ string, err error) bool function to test what types the error has

switch {
case errors.Is("Timeout", err):
    ...
case errors.Is("Unreachable", err):
    ...
default:
    ...
}

errors.Is dynamically discovers whether the error has a method matching the type name, and calls it to test the error type.

Resources:

Tagging

Tags are a list of arbitrary key/value pairs that can be attached to any errors, it provides a way to aggregate errors based on tag values. They are useful when errors are logged, traced, or measured, since the tags can be extracted from the error and injected into the logging, tracing, or metric collection systems.

To add tags to an error, a program may either define a Tags() []errors.Tag method on an error type, or use the errors.WithTags function to add tags to an error value, for example:

operation := "HelloWorld"

if err := rpc.Call(operation); err != nil {
    return errors.WithTags(err,
        errors.T("component", "rpc-client"),
        errors.T("operation", operation),
    )
}

Resources:

Causes

When wrapping an error value to add context to it (may it be types, tags, or other attributes), a program can retrieve the original error by using the errors.Cause(error) error function. This mechanism is identical to what is done in the github.com/pkg/errors package and is useful when a program needs to compare the original error value against some constant like io.EOF for example.

However, it is common in Go to end up with more than one cause for an error. When a program spawns multiple goroutine to do I/O operations in parallel, some may succeed and some may fail. Instead of having to discard all errors but the first one, or re-invent yet another multi-error type, the errors-go package offers two methods to construct error values from a set of errors:

func Join(errs ...error) error
func Recv(errs <-chan error) error

A program then cannot count on retrieving the single cause and instead can use the errors.Causes(error) []error function to extract all causes that lead to generating this error.

This means that the errors-go package allows programs to build error trees, where each node is an error value (including wrappers) with a list of causes, that may as well have been wrapped, and may also have causes themselves.

Resources:

Adapters

Due to Go's very flexible error handling model, packages have adopted different approaches, which are not always easy to plug together. This is true even within the standard library itself, where some packages use specific error types, exported or unexported error values, or a combination of those. Those differences result in heterogenous error handling code within a single program.

To work around this issue the errors-go package uses the Adapter concept. An Adapter is meant to convert errors generated by a package into errors that can be manipulated using the errors-go functions.

Adapters are intended to be registered during the initialization phase of a package (using its init function) in order to be available globally whenever an error needs to be adapted.

Errors are adapted automatically by calls to any of the wrapper functions of the errors-go package (like Wrap, WithMessage, WithStack, etc...). It means that all a program needs to do is import adapter packages and its error wrapping functions will be enhanced to do proper error decoration of errors coming from those packages.

Resources:

Formatting

Errors are eventually meant to be consumed by developers and operators of a program, which means formatting of the error values into human-readable forms is a key feature of an error package.

Go has understood this well by making the error interface's single method one that returns an error message. However, a single error message often isn't enough to communicate all the context of what went wrong, and using a format that allows the context to be fully exposed is highly valuable, both during development and operations.

Errors wrapped by, or produced by the errors-go package all use a text format which exposes the message, types, tags, stack traces, and the tree of causes that resulted in the error. Those information can be enabled or disabled based on the format string being used (e.g. %v, %+v, %#v), here's an example of the full format:

error message (type ...) [tag=value ...]
stack traces
...
├── sub error message (type ...) [tag=value ...]
|   stack traces
|   ...
└── last error message (type ...) [tag=value ...]
    stack traces
  • The first line is the error message, the types and tags of the error.
  • The stack traces are printed in the same format as the panic stack traces.
  • The tree-like format provides a representation of the tree of error causes.

This format ensures that all information carried by the error are available in a way that is both familiar with current practices (Go debug traces, format of errors from the github.com/pkg/errors package, ...) while still exposing other properties of errors of the errors-go package.

Serializing

Serializability is not a property that's very easy to obtain from Go errors. Because the standard error interface only gives access to an error message one can simply serialize this message to pass errors across services over the network, but this process would lose other information like types, tags, or causes.

To address this limitation the errors-go package uses an intermediary, serializable type to represent errors, which can also be used as a form of reflection to explore the inner structure of an error value.

The errors.ValueOf(error) function returns a errors.Value which snapshots a representation of the error passed as argument. Values can then be manipulated and serialized and deserialized by an program, and an error carrying the same properties as the original can be reconstructed from the value.

Note: The only property of an error which is not equivalent in errors built from values and their original is the stack trace. This design choice was made because programs exchanging error information over the network rarely need to carry a stack trace of a different code than theirs. So instead, the stack trace in errors that are reconstructed from values is a new capture of the call stack within the current program.

Resources:

Documentation

Overview

Package errors provides advanced error handling features.

Index

Constants

This section is empty.

Variables

View Source
var TODO error

TODO is a non-nil error intended to act as a placeholder during development when writing the structure of the code but the implementation is still left to be written.

Functions

func Adapt

func Adapt(err error) error

Adapt adapts err using the registered adapters.

Programs usually do not need to call this function explicitly and can instead rely on the fact that functions like Wrap, WithMessage, WithStack... will automatically adapt the errors that they receive.

func Cause

func Cause(err error) error

Cause returns the cause of err, which may be err if it had no cause.

func Causes

func Causes(err error) []error

Causes returns the list of causes of err, which may be an empty slice if err is nil or had no causes.

func Err

func Err(v interface{}) error

Err constructs an error from a value of arbitrary type, using the following rules:

- if v is nil, the function simply returns nil

- if v is a string type, the function behaves like calling New

- if v is already an error it is returned unchanged

- if v has an Err() method returning an error, the function returns the result of calling it.

- if v doesn't fall into any of those categories, the function behaves like calling Errorf("%+v", v).

A common use case for this function is to implement internal error reporting based on raising panics (within a package), here is an example:

func F() (err error) {
	defer func() { err = errors.Err(recover()) }()
	// ...
}

func Errorf

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

Errorf returns an error that formats as fmt.Sprintf(msg, args...). The returned error carries a capture of the stack trace.

err = errors.Errorf("unexpected answer: %d", 42)

func Inspect

func Inspect(err error) (msgs []string, types []string, tags []Tag, stacks []StackTrace, causes []error)

Inspect extract and returns properties of err.

The function follows a straight path on the error graph, stopping when it finds an error that doesn't have a single cause (either zero or many).

func Is

func Is(typ string, err error) bool

Is tests whether err is of type typ. Errors may implement types by defining methods that take no arguments and return a boolean value. Passing the name of those methods to Is tests for their existence and calls them to validate the type of the error.

This model has been used in the standard library where some errors implement the Temporary and Timeout methods to give the program more details about the reason the error occurred and the way it should be handled.

Here is an example of using the Is function:

if errors.Is("Timeout", err) {
	// ...
}
if errors.Is("Temporary", err) {
	// ...
}

The function walks through the graph of causes looking for an error which may implement the given type.

func Join

func Join(errs ...error) error

Join composes an error from the list of errors passed as argument.

The function strips all nil errors from the input argument list. The returned error has a Causes method which returns the list of non-nil errors that were given to the function.

err = errors.Join(err1, err2, err3)

All errors passed to the function are adapted.

func LookupTag

func LookupTag(err error, name string) string

LookupTag returns value for a given tag name. Returns empty string if tag wasn't found. If multiple tags found by that name, the most recent value is used.

func New

func New(msg string) error

New returns an error that formats as the given message. The returned error carries a capture of the stack trace.

err = errors.New("something went wrong")

func Recv

func Recv(ch <-chan error) error

Recv reads all errors from the given channel and returns one that combines them. All nil error are ignored.

ch := make(chan error)
wg := sync.WaitGroup{}

for _, t := range tasks {
	wg.Add(1)
	go func(t task) { ch <- t(); wg.Done() }
}

go func() { wg.Wait(); close(ch) }

err := errors.Recv(errch)

All errors received on the channel are adapted.

func Register

func Register(a Adapter)

Register registers a new error adapter.

func Types

func Types(err error) []string

Types returns a slice containing all the types implemented by err and its causes (if it had any).

func WithMessage

func WithMessage(err error, msg string) error

WithMessage returns an error that wraps err and prefix its original error error message with msg. If err is nil, WithMessage returns nil.

err = errors.WithMessage(err, "something went wrong")

func WithStack

func WithStack(err error) error

WithStack returns an error that wraps err with a capture of the stack trace at the time the function is called. If err is nil, WithStack returns nil.

err = errors.WithStack(err)

The error is adapted before the stack trace is added.

func WithStackTrace

func WithStackTrace(err error, stack StackTrace) error

WithStackTrace returns an error that wraps err with the given stack trace. If err is nil, WithStackTrace returns nil.

err = errors.WithStackTrace(err, errors.CaptureStackTrace(1))

The error is adapted before the stack trace is added.

func WithTags

func WithTags(err error, tags ...Tag) error

WithTags returns an error that wraps err and tags it with the given key/value pairs. If err is nil the function returns nil.

The error is adapted before tags are added.

func WithTypes

func WithTypes(err error, types ...string) error

WithTypes returns an error that wraps err and implements the given types so that calling errors.Is on the returned error with one of the given types will return true.

The error is adapted before types are added.

func Wrap

func Wrap(err error, msg string) error

Wrap returns an error that wraps err with msg as prefix to its original message and a capture of the stack trace at the time the function is called. If err is nil, Wrap returns nil.

err = errors.Wrap(err, "something went wrong")

The error is adapted before being wrapped with a message and stack trace.

func Wrapf

func Wrapf(err error, msg string, args ...interface{}) error

Wrapf returns an error that wraps err with fmt.Sprintf(msg, args...) as prefix to its original message and a capture of the stack trace at the time the function is called. If err is nil, Wrap returns nil.

err = errors.Wrapf(err, "unexpected answer: %d", 42)

The error is adapted before being wrapped with a message and stack trace.

Types

type Adapter

type Adapter interface {
	// Adapt is called to adapt err, it either returnes err and false if it did
	// not recognize the error, or returns the adapted error and true.
	Adapt(err error) (error, bool)
}

Adapter is an interface implemented by types that support adapting errors to be introspected by functions of the erorrs package.

type AdapterFunc

type AdapterFunc func(error) (error, bool)

The AdapterFunc types is an implementation of the Adapter interface which makes it possible to use simple functions as error adapters.

func (AdapterFunc) Adapt

func (f AdapterFunc) Adapt(err error) (error, bool)

Adapt satisfies the Adapter interface, calls f.

type Frame

type Frame uintptr

Frame represents a program counter inside a stack frame.

func (Frame) Format

func (f Frame) Format(s fmt.State, verb rune)

Format formats the frame according to the fmt.Formatter interface.

%s    source file
%d    source line
%n    function name
%v    equivalent to %s:%d

Format accepts flags that alter the printing of some verbs, as follows:

%+s   path of source file relative to the compile time GOPATH
%#s   function name and path of source file
%+n   function name prefixed by its package name
%#n   function name prefixed by its full package path
%+v   equivalent to %+s:%d
%#v   equivalent to %#s:%d

type StackTrace

type StackTrace []Frame

StackTrace is stack of Frames from innermost (newest) to outermost (oldest).

func CaptureStackTrace

func CaptureStackTrace(skip int) StackTrace

CaptureStackTrace walks the call stack that led to this function and records it as a StackTrace value. The skip argument is the number of stacks frames to skip, the frame for captureStackTrace is never included in the returned trace.

func (StackTrace) Format

func (st StackTrace) Format(s fmt.State, verb rune)

Format formats the stack of Frames according to the fmt.Formatter interface.

%s	lists source files for each Frame in the stack
%v	lists the source file and line number for each Frame in the stack

Format accepts flags that alter the printing of some verbs, as follows:

%+v   Prints filename, function, and line number for each Frame in the stack.

type Tag

type Tag struct {
	Name  string
	Value string
}

Tag is a key/value type used to represent a single error tag.

func T

func T(name string, value string) Tag

T returns a Tag value with the given name and value.

func Tags

func Tags(err error) []Tag

Tags returns a slice containing all the tags set on err and its causes (it if had any).

type Value

type Value struct {
	Message string
	Tags    map[string]string
	Types   []string
	Stack   []string
	Causes  []Value
}

Value is a serializable error representation which carries all rich information of errors that can be constructed by this package.

This type is useful to transmit errors between programs that communicate over some IPC mechanism. It may also be used as a form of reflection to discover the various components of an error.

func ValueOf

func ValueOf(err error) Value

ValueOf returns an error value representing err. If err is nil the function returns the zero-value of Value.

func (Value) Err

func (v Value) Err() error

Err constructs and returns an error from v, the error message, types, and causes are rebuilt and part of the returned error to match as closely as possible the information carried by the error that this value was built from in the first place.

Note that the only information that isn't carried back from the Value into the returned error is the stack trace, because it may be coming from a different program there is no way to match it to the correct function pointers. Instead the stack trace information in the returned error is set to the call stack that led to this method call, which in general is way more relevant to the program which is calling this method.

If v is the zero-value, the method returns a nil error.

func (Value) IsNil

func (v Value) IsNil() bool

IsNil returns true if v represents a nil error (which means it is the zero-value).

Directories

Path Synopsis
Package awserrors provides functions to adapt errors of the AWS Go SDK into errors compatible with the errors-go package.
Package awserrors provides functions to adapt errors of the AWS Go SDK into errors compatible with the errors-go package.
Package httperrors provides functions to construct errors from HTTP responses.
Package httperrors provides functions to construct errors from HTTP responses.
Package ioerrors provides adapters for errors generated by the standard io package.
Package ioerrors provides adapters for errors generated by the standard io package.
Package neterrors provides adapters for errors generated by the standard net package.
Package neterrors provides adapters for errors generated by the standard net package.
Package pkgerrors provides adapters for errors generated by the github.com/pkg/errors package.
Package pkgerrors provides adapters for errors generated by the github.com/pkg/errors package.
Package stderrors exposes no APIs and is used for the sole purpose of setting up global adapters for all packages of the standard library supported by the errors-go project.
Package stderrors exposes no APIs and is used for the sole purpose of setting up global adapters for all packages of the standard library supported by the errors-go project.
Package twirperrors provides functions to adapt errors of the github.com/twitchtv/twirp package into errors compatible with the errors-go package.
Package twirperrors provides functions to adapt errors of the github.com/twitchtv/twirp package into errors compatible with the errors-go package.

Jump to

Keyboard shortcuts

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