Package errors
provides simple error handling primitives that work well with structured logging.
This package is deprecated. Use the kv package instead. It provides
support for creating errors with key/value pairs. The idea of having a drop-in replacement for the
standard library errors
package was based on the fact that errors
only exported one simple function.
The Go 2 draft proposes additional functions and types for the standard library errors
package, and
any attempts to use the new standard library package with this package will be annoying.
- Acknowledgement
- Background
- Creating errors
- Retrieving the cause of an error
- Retrieving key value pairs for structured logging
This package is inspired by the excellent github.com/pkg/errors package. A significant amount of code and documentation in this package has been adapted from that source.
A key difference between this package and github.com/pkg/errors is that this package has been designed to suit programs that make use of structured logging. Some of the ideas in this package were proposed for package github.com/pkg/errors, but after a reasonable amount of consideration, were ultimately not included in that package.
If you are not using structured logging in your application and have no intention of doing so, you will probably be better off using the github.com/pkg/errors package in preference to this one.
The traditional error handling idiom in Go is roughly akin to
if err != nil {
return err
}
which applied recursively up the call stack results in error reports without context or debugging information. The errors
package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
The errors
package provides three operations which combine to form a simple yet powerful system for enhancing the value of
returned errors:
Operation | Description |
---|---|
New | create a new error |
Wrap | wrap an existing error with an optional message |
With | attach key/value pairs to an error |
The New
function
is used to create an error. This function is compatible with the Go standard
library errors
package:
err := errors.New("emit macho dwarf: elf header corrupted")
The Wrap
function
returns an error that adds a message to the original error. This additional
message can be useful for putting the original error in context. For example:
err := errors.New("permission denied")
fmt.Println(err)
err = errors.Wrap(err, "cannot list directory contents")
fmt.Println(err)
// Output:
// permission denied
// cannot list directory contents: permission denied
The With
function
accepts a variadic list of alternating key/value pairs, and returns an error
context that can be used to create a new error or wrap an existing error.
// create new error
err = errors.With("file", "testrun", "line", 101).New("file locked")
fmt.Println(err)
// wrap existing error
err = errors.With("attempt", 3).Wrap(err, "retry failed")
fmt.Println(err)
// Output:
// file locked file=testrun line=101
// retry failed attempt=3: file locked file=testrun line=101
One useful pattern is to create an error context that is used for an entire function scope:
func doSomethingWith(file string, line int) error {
// set error context
errors := errors.With("file", file, "line", line)
if number <= 0 {
// file and line will be attached to the error
return errors.New("invalid number")
}
// ... later ...
if err := doOneThing(); err != nil {
// file and line will be attached to the error
return errors.Wrap(err, "cannot do one thing")
}
// ... and so on until ...
return nil
}
The errors returned by New
and Wrap
provide a With
method that enables
a fluent-style of error handling:
// create new error
err = errors.New("file locked").With(
"file", "testrun",
"line", 101,
)
fmt.Println(err)
// wrap existing error
err = errors.Wrap(err, "retry failed").With("attempt", 3)
fmt.Println(err)
// Output:
// file locked file=testrun line=101
// retry failed attempt=3: file locked file=testrun line=101
(Dave Cheney has written up some good reasons to avoid a fluent API. Experience will show if this presents a problem, but to date it has felt like it leads to simpler, more readable code).
Using errors.Wrap
constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap
to retrieve the original error for inspection. Any error value which implements this interface can be inspected by errors.Cause
.
type causer interface {
Cause() error
}
errors.Cause
will recursively retrieve the topmost error which does not implement causer
, which is assumed to be the original cause. For example:
switch err := errors.Cause(err).(type) {
case *MyError:
// handle specifically
default:
// unknown error
}
Errors created by errors.Wrap
and errors.New
implement the following interface.
type keyvalser interface {
Keyvals() []interface{}
}
The Keyvals
method returns an array of alternating keys and values. The
first key will always be "msg" and its value will be a string containing
the message associated with the wrapped error.
Example using go-kit logging:
// logError logs details of an error to a structured error log.
func logError(logger log.Logger, err error) {
// start with timestamp and error level
keyvals := []interface{}{
"ts", time.Now().Format(time.RFC3339Nano),
"level", "error",
}
type keyvalser interface {
Keyvals() []interface{}
}
if kv, ok := err.(keyvalser); ok {
// error contains structured information, first key/value
// pair will be "msg".
keyvals = append(keyvals, kv.Keyvals()...)
} else {
// error does not contain structured information, use the
// Error() string as the message.
keyvals = append(keyvals, "msg", err.Error())
}
logger.Log(keyvals...)
}
GOOD ADVICE: Do not use the
Keyvals
method on an error to retrieve the individual key/value pairs associated with an error for processing by the calling program.
Read the package documentation for more information.
MIT