Documentation ¶
Overview ¶
Package errors provides errors with a recorded stack trace.
The traditional error handling idiom in Go is roughly akin to
if err != nil { return err }
which when applied recursively up the call stack results in error reports without a stack trace or context. The errors package provides error handling primitives to annotate errors along the failure path in a way that does not destroy the original error.
Adding a stack trace to an error ¶
When interacting with code which returns errors without a stack trace, you can upgrade that error to one with a stack trace using errors.WithStack. For example:
func readAll(r io.Reader) ([]byte, errors.E) { data, err := ioutil.ReadAll(r) if err != nil { return nil, errors.WithStack(err) } return data, nil }
errors.WithStack records the stack trace at the point where it was called, so use it as close to where the error originated as you can get so that the recorded stack trace is more precise.
The example above uses errors.E for the returned error type instead of the standard error type. This is not required, but it tells Go that you expect that the function returns only errors with a stack trace and Go type system then helps you find any cases where this is not so.
errors.WithStack does not record the stack trace if it is already present in the error so it is safe to call it if you are unsure if the error contains a stack trace.
Errors with a stack trace implement the following interface, returning program counters of function invocations:
type stackTracer interface { StackTrace() []uintptr }
You can use standard runtime.CallersFrames to obtain stack trace frame information (e.g., function name, source code file and line).
Although the stackTracer interface is not exported by this package, it is considered a part of its stable public interface.
Adding context to an error ¶
Sometimes an error occurs in a low-level function and the error messages returned from it are too low-level, too. You can use errors.Wrap to construct a new higher-level error while recording the original error as a cause.
image, err := readAll(imageFile) if err != nil { return nil, errors.Wrap(err, "reading image failed") }
In the example above we returned a new error with a new message, hidding the low-level details. The returned error implements the following interface
type causer interface { Cause() error }
which enables access to the underlying low-level error. You can also use errors.Cause to obtain the cause.
Although the causer interface is not exported by this package, it is considered a part of its stable public interface.
Sometimes you do not want to hide the error message but just add to it. You can use errors.WithMessage, which adds a prefix to the existing message, or errors.Errorf, which gives you more control over the new message.
errors.WithMessage(err, "reading image failed") errors.Errorf("reading image failed (%w)", err)
Example new messages could then be, respectively:
"reading image failed: connection error" "reading image failed (connection error)"
Adding details to an error ¶
Errors returned by this package implement the detailer interface
type detailer interface { Details() map[string]interface{} }
which enables access to a map with optional additional information about the error. Returned map can be modified in-place to store additional information. You can also use errors.Details and errors.AllDetails to access details.
Working with the hierarchy of errors ¶
Errors which implement the following standard unwrapper interface
type unwrapper interface { Unwrap() error }
form a hierarchy of errors where a wrapping error points its parent, wrapped, error. Errors returned from this package implement this interface to return the original error, when there is one. This enables us to have constant base errors which we annotate with a stack trace before we return them:
var AuthenticationError = errors.Base("authentication error") var MissingPassphraseError = errors.BaseWrap(AuthenticationError, "missing passphrase") var InvalidPassphraseError = errors.BaseWrap(AuthenticationError, "invalid passphrase") func authenticate(passphrase string) errors.E { if passphrase == "" { return errors.WithStack(MissingPassphraseError) } else if passphrase != "open sesame" { return errors.WithStack(InvalidPassphraseError) } return nil }
We can use errors.Is to determine which error has been returned:
if errors.Is(err, MissingPassphraseError) { fmt.Println("Please provide a passphrase to unlock the doors.") }
Works across the hierarchy, too:
if errors.Is(err, AuthenticationError) { fmt.Println("Failed to unlock the doors.") }
Formatting errors ¶
All errors with a stack trace returned from this package implement fmt.Formatter interface and can be formatted by the fmt package. The following verbs are supported:
%s the error message %v same as %s %+v together with the error message include also the stack trace, ends with a newline
Example (StackTrace) ¶
package main import ( "fmt" "runtime" "gitlab.com/tozd/go/errors" ) func getErr() error { return errors.New("foobar") } func main() { type stackTracer interface { StackTrace() []uintptr } err, ok := getErr().(stackTracer) if !ok { panic(errors.New("oops, err does not implement stackTracer")) } frames := runtime.CallersFrames(err.StackTrace()) frame, _ := frames.Next() fmt.Printf("%s\n\t%s:%d", frame.Function, frame.File, frame.Line) // Example output: // gitlab.com/tozd/go/errors_test.getErr // /home/user/errors/example_test.go:213 }
Output:
Index ¶
- func AllDetails(err error) map[string]interface{}
- func As(err error, target interface{}) bool
- func Base(message string) error
- func BaseWrap(err error, message string) error
- func BaseWrapf(err error, format string, args ...interface{}) error
- func Basef(format string, args ...interface{}) error
- func Cause(err error) error
- func Details(err error) map[string]interface{}
- func Is(err, target error) bool
- func Unwrap(err error) error
- type E
- func Errorf(format string, args ...interface{}) E
- func New(message string) E
- func WithDetails(err error) E
- func WithMessage(err error, message string) E
- func WithMessagef(err error, format string, args ...interface{}) E
- func WithStack(err error) E
- func Wrap(err error, message string) E
- func Wrapf(err error, format string, args ...interface{}) E
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AllDetails ¶ added in v0.4.0
AllDetails returns a map build from calling The Details method on err and populating the map with key/value pairs which are not yet present. Afterwards, the err is unwrapped and the process is repeated.
func As ¶
As finds the first error in err's chain that matches target, and if so, sets target to that error value and returns true. Otherwise, it returns false.
The chain consists of err itself followed by the sequence of errors obtained by repeatedly calling Unwrap.
An error matches target if the error's concrete value is assignable to the value pointed to by target, or if the error has a method As(interface{}) bool such that As(target) returns true. In the latter case, the As method is responsible for setting target.
An error type might provide an As method so it can be treated as if it were a different error type.
As panics if target is not a non-nil pointer to either a type that implements error, or to any interface type.
This function is a proxy for standard errors.As.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) type MyError struct { code int message string } func (e MyError) Error() string { return e.message } func (e MyError) Code() int { return e.code } var ( BadRequestError = &MyError{400, "error"} NotFoundError = &MyError{404, "not found"} ) func getMyErr() error { return NotFoundError } func main() { err := getMyErr() var myErr *MyError if errors.As(err, &myErr) { fmt.Printf("code: %d", myErr.Code()) } }
Output: code: 404
func Base ¶
Base returns an error with the supplied message. Each call to Base returns a distinct error value even if the message is identical. It does not record a stack trace.
Use this for a constant base error you convert to an actual error you return with WithStack. This base error you can then use in Is and As calls.
This function is a proxy for standard errors.New.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { err := errors.Base("whoops") fmt.Println(err) }
Output: whoops
func BaseWrap ¶
BaseWrap returns an error with the supplied message, wrapping an existing error err. Each call to BaseWrap returns a distinct error value even if the message is identical. It does not record a stack trace.
Use this when you want to create a hierarchy of base errors and you want to fully control the error message.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { base := errors.Base("error") valueError := errors.BaseWrap(base, "value") fmt.Println(valueError) }
Output: value
func BaseWrapf ¶
BaseWrapf returns an error with the supplied message formatted according to a format specifier. Each call to BaseWrapf returns a distinct error value even if the message is identical. It does not record a stack trace. It does not support %w format verb. Use Basef if you need it.
Use this when you want to create a hierarchy of base errors and you want to fully control the error message.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { base := errors.Base("error") valueError := errors.BaseWrapf(base, "value %d", 2) fmt.Println(valueError) }
Output: value 2
func Basef ¶
Basef returns an error with the supplied message formatted according to a format specifier. Each call to Basef returns a distinct error value even if the message is identical. It does not record a stack trace. It supports %w format verb to wrap an existing error.
Use this for a constant base error you convert to an actual error you return with WithStack. This base error you can then use in Is and As calls. Use %w format verb when you want to create a hierarchy of base errors.
This function is a proxy for standard fmt.Errorf.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { err := errors.Basef("whoops #%d", 2) fmt.Println(err) }
Output: whoops #2
func Cause ¶
Cause returns the result of calling the Cause method on err, if err's type contains an Cause method returning error. Otherwise, the err is unwrapped and the process is repeated. If unwrapping is not possible, Cause returns nil.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { base := errors.Base("error") wrapped := errors.Wrap(base, "wrapped") fmt.Println(errors.Cause(wrapped)) }
Output: error
func Details ¶ added in v0.4.0
Details returns the result of calling the Details method on err, or nil if it is not available.
func Is ¶
Is reports whether any error in err's chain matches target.
The chain consists of err itself followed by the sequence of errors obtained by repeatedly calling Unwrap.
An error is considered to match a target if it is equal to that target or if it implements a method Is(error) bool such that Is(target) returns true.
An error type might provide an Is method so it can be treated as equivalent to an existing error. For example, if MyError defines
func (m MyError) Is(target error) bool { return target == fs.ErrExist }
then Is(MyError{}, fs.ErrExist) returns true. See syscall.Errno.Is for an example in the standard library.
This function is a proxy for standard errors.Is.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { base := errors.Base("error") valueError := errors.BaseWrap(base, "value") fmt.Println(errors.Is(valueError, base)) }
Output: true
func Unwrap ¶
Unwrap returns the result of calling the Unwrap method on err, if err's type contains an Unwrap method returning error. Otherwise, Unwrap returns nil.
This function is a proxy for standard errors.Unwrap.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { base := errors.Base("error") withPrefix := errors.WithMessage(base, "prefix") fmt.Println(withPrefix) fmt.Println(errors.Unwrap(withPrefix)) }
Output: prefix: error error
Types ¶
type E ¶
type E interface { error // contains filtered or unexported methods }
E interface can be used in as a return type instead of the standard error interface to annotate which functions return an error with a stack trace. This is useful so that you know when you should use WithStack (for functions which do not return E) and when not (for functions which do return E). If you call WithStack on an error with a stack trace nothing bad happens (same error is simply returned), it just pollutes the code. So this interface is defined to help.
func Errorf ¶
Errorf return an error with the supplied message formatted according to a format specifier. It supports %w format verb to wrap an existing error. Errorf also records the stack trace at the point it was called, unless wrapped error already have a stack trace.
When formatting the returned error using %+v, formatting is not delegated to the wrapped error (when there is one), giving you full control of the message and formatted error.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { err := errors.Errorf("whoops: %s", "foo") fmt.Printf("%+v", err) // Example output: // whoops: foo // Stack trace (most recent call first): // gitlab.com/tozd/go/errors_test.ExampleErrorf // /home/user/errors/example_test.go:134 // testing.runExample // /usr/local/go/src/testing/run_example.go:64 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1505 // main.main // _testmain.go:95 // runtime.main // /usr/local/go/src/runtime/proc.go:255 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1581 }
Output:
Example (Wrap) ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { base := errors.Base("whoops") err := errors.Errorf("oh noes (%w)", base) fmt.Printf("%+v", err) // Example output: // oh noes (whoops) // Stack trace (most recent call first): // gitlab.com/tozd/go/errors_test.ExampleErrorf_wrap // /home/user/errors/example_test.go:189 // testing.runExample // /usr/local/go/src/testing/run_example.go:64 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1505 // main.main // _testmain.go:99 // runtime.main // /usr/local/go/src/runtime/proc.go:255 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1581 }
Output:
func New ¶
New returns an error with the supplied message. New also records the stack trace at the point it was called.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { err := errors.New("whoops") fmt.Println(err) }
Output: whoops
Example (Printf) ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { err := errors.New("whoops") fmt.Printf("%+v", err) // Example output: // whoops // Stack trace (most recent call first): // gitlab.com/tozd/go/errors_test.ExampleNew_printf // /home/user/errors/example_test.go:16 // testing.runExample // /usr/local/go/src/testing/run_example.go:64 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1505 // main.main // _testmain.go:87 // runtime.main // /usr/local/go/src/runtime/proc.go:255 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1581 }
Output:
func WithDetails ¶ added in v0.4.0
WithDetails wraps err implementing the detailer interface to access a map with optional additional information about the error.
If err does not have a stack trace, then this call is equivalent to calling WithStack, annotating err with a stack trace as well.
Use this when you have an err which implements stackTracer interface but does not implement detailer interface as well.
It is useful when err does implement detailer interface, but you want to reuse same err multiple times (e.g., pass same err to multiple goroutines), adding different details each time. Calling WithDetails wraps err and adds an additional and independent layer of details on top of any existing details.
func WithMessage ¶
WithMessage annotates err with a prefix message. If err does not have a stack trace, stack strace is recorded as well.
It does not support controlling the delimiter. Use Errorf if you need that.
When formatting the returned error using %+v, formatting is delegated to the wrapped error, prefixing it with the message. The stack trace is added only if the wrapped error does not already have it.
If err is nil, WithMessage returns nil.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { cause := errors.New("whoops") err := errors.WithMessage(cause, "oh noes") fmt.Println(err) }
Output: oh noes: whoops
Example (Printf) ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { cause := errors.New("whoops") err := errors.WithMessage(cause, "oh noes") fmt.Printf("%+v", err) // Example output: // oh noes: whoops // Stack trace (most recent call first): // gitlab.com/tozd/go/errors_test.ExampleWithMessage_printf // /home/user/errors/example_test.go:46 // testing.runExample // /usr/local/go/src/testing/run_example.go:64 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1505 // main.main // _testmain.go:97 // runtime.main // /usr/local/go/src/runtime/proc.go:255 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1581 }
Output:
func WithMessagef ¶
WithMessagef annotates err with a prefix message formatted according to a format specifier. If err does not have a stack trace, stack strace is recorded as well.
It does not support %w format verb or controlling the delimiter. Use Errorf if you need that.
When formatting the returned error using %+v, formatting is delegated to the wrapped error, prefixing it with the message. The stack trace is added only if the wrapped error does not already have it.
If err is nil, WithMessagef returns nil.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { cause := errors.New("whoops") err := errors.WithMessagef(cause, "oh noes #%d", 2) fmt.Println(err) }
Output: oh noes #2: whoops
func WithStack ¶
WithStack annotates err with a stack trace at the point WithStack was called, if err does not already have a stack trace. If err is nil, WithStack returns nil.
When formatting the returned error using %+v, formatting is delegated to the wrapped error. The stack trace is added only if the wrapped error does not already have it.
Use this instead of Wrap when you just want to convert an existing error into one with a stack trace. Use it as close to where the error originated as you can get.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { base := errors.Base("whoops") err := errors.WithStack(base) fmt.Println(err) }
Output: whoops
Example (Printf) ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { base := errors.Base("whoops") err := errors.WithStack(base) fmt.Printf("%+v", err) // Example output: // whoops // Stack trace (most recent call first): // gitlab.com/tozd/go/errors_test.ExampleWithStack_printf // /home/user/errors/example_test.go:54 // testing.runExample // /usr/local/go/src/testing/run_example.go:64 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1505 // main.main // _testmain.go:91 // runtime.main // /usr/local/go/src/runtime/proc.go:255 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1581 }
Output:
func Wrap ¶
Wrap returns an error annotating err with a stack trace at the point Wrap is called, and the supplied message. Wrapping is done even if err already has a stack trace. It records the original error as a cause. If err is nil, Wrap returns nil.
When formatting the returned error using %+v, formatting of the cause is delegated to the wrapped error.
Use this when you want to make a new error, preserving the cause of the new error.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { cause := errors.New("whoops") err := errors.Wrap(cause, "oh noes") fmt.Println(err) }
Output: oh noes
Example (Printf) ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { cause := errors.New("whoops") err := errors.Wrap(cause, "oh noes") fmt.Printf("%+v", err) // Example output: // oh noes // Stack trace (most recent call first): // gitlab.com/tozd/go/errors_test.ExampleWrap_printf // /home/user/errors/example_test.go:86 // testing.runExample // /usr/local/go/src/testing/run_example.go:64 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1505 // main.main // _testmain.go:93 // runtime.main // /usr/local/go/src/runtime/proc.go:255 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1581 // // The above error was caused by the following error: // // whoops // Stack trace (most recent call first): // gitlab.com/tozd/go/errors_test.ExampleWrap_printf // /home/user/errors/example_test.go:85 // testing.runExample // /usr/local/go/src/testing/run_example.go:64 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1505 // main.main // _testmain.go:93 // runtime.main // /usr/local/go/src/runtime/proc.go:255 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1581 }
Output:
func Wrapf ¶
Wrapf returns an error annotating err with a stack trace at the point Wrapf is called, and the supplied message formatted according to a format specifier. Wrapping is done even if err already has a stack trace. It records the original error as a cause. It does not support %w format verb. If err is nil, Wrapf returns nil.
When formatting the returned error using %+v, formatting of the cause is delegated to the wrapped error.
Use this when you want to make a new error, preserving the cause of the new error.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { base := errors.Base("whoops") err := errors.Wrapf(base, "oh noes #%d", 2) fmt.Println(err) }
Output: oh noes #2