Eris
Error handling library with readable stack traces and flexible formatting support 🎆
Install / Use
/learn @rotisserie/ErisREADME
eris
[![GoDoc][doc-img]][doc] [![Build][ci-img]][ci] [![GoReport][report-img]][report] [![Coverage Status][cov-img]][cov]
Package eris is an error handling library with readable stack traces and flexible formatting support.
go get github.com/rotisserie/eris
- Why you should switch to eris
- Using eris
- Comparison to other packages (e.g. pkg/errors)
- Migrating to eris
- Contributing
Why you should switch to eris
This package was inspired by a simple question: what if you could fix a bug without wasting time replicating the issue or digging through the code? With that in mind, this package is designed to give you more control over error handling via error wrapping, stack tracing, and output formatting.
The example that generated the output below simulates a realistic error handling scenario and demonstrates how to wrap and log errors with minimal effort. This specific error occurred because a user tried to access a file that can't be located, and the output shows a clear path from the top of the call stack to the source.
{
"error":{
"root":{
"message":"error internal server",
"stack":[
"main.main:/Users/roti/go/src/github.com/rotisserie/eris/examples/logging/example.go:143",
"main.ProcessResource:/Users/roti/go/src/github.com/rotisserie/eris/examples/logging/example.go:85",
"main.ProcessResource:/Users/roti/go/src/github.com/rotisserie/eris/examples/logging/example.go:82",
"main.GetRelPath:/Users/roti/go/src/github.com/rotisserie/eris/examples/logging/example.go:61"
]
},
"wrap":[
{
"message":"failed to get relative path for resource 'res2'",
"stack":"main.ProcessResource:/Users/roti/go/src/github.com/rotisserie/eris/examples/logging/example.go:85"
},
{
"message":"Rel: can't make ./some/malformed/absolute/path/data.json relative to /Users/roti/",
"stack":"main.GetRelPath:/Users/roti/go/src/github.com/rotisserie/eris/examples/logging/example.go:61"
}
]
},
"level":"error",
"method":"ProcessResource",
"msg":"method completed with error",
"time":"2020-01-16T11:20:01-05:00"
}
Many of the methods in this package will look familiar if you've used pkg/errors or xerrors, but eris employs some additional tricks during error wrapping and unwrapping that greatly improve the readability of the stack trace. This package also takes a unique approach to formatting errors that allows you to write custom formats that conform to your error or log aggregator of choice. You can find more information on the differences between eris and pkg/errors here.
Using eris
Creating errors
Creating errors is simple via eris.New.
var (
// global error values can be useful when wrapping errors or inspecting error types
ErrInternalServer = eris.New("error internal server")
)
func (req *Request) Validate() error {
if req.ID == "" {
// or return a new error at the source if you prefer
return eris.New("error bad request")
}
return nil
}
Wrapping errors
eris.Wrap adds context to an error while preserving the original error.
relPath, err := GetRelPath("/Users/roti/", resource.AbsPath)
if err != nil {
// wrap the error if you want to add more context
return nil, eris.Wrapf(err, "failed to get relative path for resource '%v'", resource.ID)
}
Formatting and logging errors
eris.ToString and eris.ToJSON should be used to log errors with the default format (shown above). The JSON method returns a map[string]interface{} type for compatibility with Go's encoding/json package and many common JSON loggers (e.g. logrus).
// format the error to JSON with the default format and stack traces enabled
formattedJSON := eris.ToJSON(err, true)
fmt.Println(json.Marshal(formattedJSON)) // marshal to JSON and print
logger.WithField("error", formattedJSON).Error() // or ideally, pass it directly to a logger
// format the error to a string and print it
formattedStr := eris.ToString(err, true)
fmt.Println(formattedStr)
eris also enables control over the default format's separators and allows advanced users to write their own custom output format.
Interpreting eris stack traces
Errors created with this package contain stack traces that are managed automatically. They're currently mandatory when creating and wrapping errors but optional when printing or logging. By default, the stack trace and all wrapped layers follow the opposite order of Go's runtime package, which means that the original calling method is shown first and the root cause of the error is shown last.
{
"root":{
"message":"error bad request", // root cause
"stack":[
"main.main:/Users/roti/go/src/github.com/rotisserie/eris/examples/logging/example.go:143", // original calling method
"main.ProcessResource:/Users/roti/go/src/github.com/rotisserie/eris/examples/logging/example.go:71",
"main.(*Request).Validate:/Users/roti/go/src/github.com/rotisserie/eris/examples/logging/example.go:29", // location of Wrap call
"main.(*Request).Validate:/Users/roti/go/src/github.com/rotisserie/eris/examples/logging/example.go:28" // location of the root
]
},
"wrap":[
{
"message":"received a request with no ID", // additional context
"stack":"main.(*Request).Validate:/Users/roti/go/src/github.com/rotisserie/eris/examples/logging/example.go:29" // location of Wrap call
}
]
}
Inverting the stack trace and error output
If you prefer some other order than the default, eris supports inverting both the stack trace and the entire error output. When both are inverted, the root error is shown first and the original calling method is shown last.
// create a default format with error and stack inversion options
format := eris.NewDefaultStringFormat(eris.FormatOptions{
InvertOutput: true, // flag that inverts the error output (wrap errors shown first)
WithTrace: true, // flag that enables stack trace output
InvertTrace: true, // flag that inverts the stack trace output (top of call stack shown first)
})
// format the error to a string and print it
formattedStr := eris.ToCustomString(err, format)
fmt.Println(formattedStr)
// example output:
// error not found
// main.GetResource:/Users/roti/go/src/github.com/rotisserie/eris/examples/logging/example.go:52
// main.ProcessResource:/Users/roti/go/src/github.com/rotisserie/eris/examples/logging/example.go:76
// main.main:/Users/roti/go/src/github.com/rotisserie/eris/examples/logging/example.go:143
// failed to get resource 'res1'
// main.GetResource:/Users/roti/go/src/github.com/rotisserie/eris/examples/logging/example.go:52
Inspecting errors
The eris package provides a couple ways to inspect and compare error types. eris.Is returns true if a particular error appears anywhere in the error chain. Currently, it works simply by comparing error messages with each other. If an error contains a particular message (e.g. "error not found") anywhere in its chain, it's defined to be that error type.
ErrNotFound := eris.New("error not found")
_, err := db.Get(id)
// check if the resource was not found
if eris.Is(err, ErrNotFound) {
// return the error with some useful context
return eris.Wrapf(err, "error getting resource '%v'", id)
}
eris.As finds the first error in a chain that matches a given target. If there's a match, it sets the target to that error value and returns true.
var target *NotFoundError
_, err := db.Get(id)
// check if the error is a NotFoundError type
if errors.As(err, &target) {
// err is a *NotFoundError and target is set to the error's value
return target
}
eris.Cause unwraps an error until it reaches the cause, which is defined as the first (i.e. root) error in the chain.
ErrNotFound := eris.New("error not found")
_, err := db.Get(id)
// compare the cause to some sentinel value
if eris.Cause(err) == ErrNotFound {
// return the error with some useful context
return eris.Wrapf(err, "error getting resource '%v'", id)
}
Formatting with custom separators
For users who need more control over the error output, eris allows for some control over the separators between each piece of the output via the eris.Format type. If this isn't flexible enough for your needs, see the [custom output
