Bubble up errors in Golang
One problem I often encounter is errors without any context.
If an arbitrary method returns an error “invalid character ‘}’ after object key”, you will have to dive down the stack until you find
the exact place the error happened. Therefore whenever you receive an error and you decide to not handle it, but instead
return it to the caller of your method, you should provide context about the reason the error occurred.
The examples below use errors.Wrap from github.com/pkg/errors.
Let’s take the following method as example:
func main() {
err := process([]byte("some_invalid_input"))
if err != nil {
panic(err)
}
}
func process(input []byte) error {
r, err := serviceA.Process(input)
if err != nil {
return err
}
err = serviceB.Process(r)
if err != nil {
return err
}
return nil
}If serviceA or serviceB returns an error, we don’t know which one failed and we don’t know whether it is our fault or whether the service is unreachable or misconfigured. The error could look like this:
panic: invalid character '}' looking for beginning of value
goroutine 1 [running]:
main.main()
/Users/christophportmann/workspace/go/src/github.com/chrisport/go-playground/errorhandling/main.go:10 +0x92
exit status 2By wrapping the error, we can provide the missing information about what part failed:
func main() {
err := process([]byte("some_invalid_input"))
if err != nil {
panic(err)
}
}
func process(input []byte) error {
r, err := serviceA.Process(input)
if err != nil {
return errors.Wrap(err,"call to service A failed")
}
err = serviceB.Process(r)
if err != nil {
return errors.Wrap(err,"call to service B failed")
}
return nil
}The result would be unambiguous:
panic: call to service B failed: invalid character '}' looking for beginning of value
goroutine 1 [running]:
main.main()
/Users/christophportmann/workspace/go/src/github.com/chrisport/go-playground/errorhandling/main.go:11 +0x92
exit status 2In this simple example the advantage is not crystal clear. But imagine serviceA is doing a complex task itself and doesn’t wrap its errors either.
You could end up getting generic errors (e.g. a timeout on an http call) and need to debug down the whole stack to find out which
call has failed. But if errors are always wrapped with necessary context, an error message could look as nice as this:
panic: call to service B failed: call to backend failed: http: wrote more than the declared Content-LengthTherefore if you decide to bubble up an error, always provide context.