Documentation ¶
Overview ¶
Package errors provides errors with a recorded stack trace and optional structured details.
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). You can also use errors.StackFormatter to format the stack trace.
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, hiding 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 details about the error. Returned map can be modified in-place. You can also use errors.Details and errors.AllDetails to access details:
errors.Details(err)["url"] = "http://example.com"
You can also use errors.WithDetails as an alternative to errors.WithStack if you also want to add details while recording the stack trace:
func readAll(r io.Reader, filename string) ([]byte, errors.E) { data, err := ioutil.ReadAll(r) if err != nil { return nil, errors.WithDetails(err, "filename", filename) } return data, nil }
Working with the tree of errors ¶
Errors which implement the following standard unwrapper interfaces:
type unwrapper interface { Unwrap() error } type unwrapper interface { Unwrap() error[] }
form a tree of errors where a wrapping error points its parent, wrapped, error(s). Errors returned from this package implement this interface to return the original error or errors, when they exist. This enables us to have constant base errors which we annotate with a stack trace before we return them:
var ErrAuthentication = errors.Base("authentication error") var ErrMissingPassphrase = errors.BaseWrap(ErrAuthentication, "missing passphrase") var ErrInvalidPassphrase = errors.BaseWrap(ErrAuthentication, "invalid passphrase") func authenticate(passphrase string) errors.E { if passphrase == "" { return errors.WithStack(ErrMissingPassphrase) } else if passphrase != "open sesame" { return errors.WithStack(ErrInvalidPassphrase) } return nil }
Or with details:
func authenticate(username, passphrase string) errors.E { if passphrase == "" { return errors.WithDetails(ErrMissingPassphrase, "username", username) } else if passphrase != "open sesame" { return errors.WithDetails(ErrInvalidPassphrase, "username", username) } return nil }
We can use errors.Is to determine which error has been returned:
if errors.Is(err, ErrMissingPassphrase) { fmt.Println("Please provide a passphrase to unlock the doors.") }
Works across the tree, too:
if errors.Is(err, ErrAuthentication) { fmt.Println("Failed to unlock the doors.") }
To access details, use:
errors.AllDetails(err)["username"]
You can join multiple errors into one error by calling errors.Join. Join also records the stack trace at the point it was called.
Formatting and JSON marshaling errors ¶
All errors with a stack trace returned from this package implement fmt.Formatter interface and can be formatted by the fmt package. They also support marshaling to JSON. Same formatting and JSON marshaling for errors coming outside of this package can be done by wrapping them into errors.Formatter.
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 } var err stackTracer if !errors.As(getErr(), &err) { 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:11 }
Output:
Index ¶
- Variables
- 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 Unjoin(err error) []error
- func Unwrap(err error) error
- type E
- func Errorf(format string, args ...interface{}) E
- func Join(errs ...error) E
- func New(message string) E
- func Prefix(err error, prefix ...error) E
- func UnmarshalJSON(data []byte) (error, E)
- func WithDetails(err error, kv ...interface{}) E
- func WithMessage(err error, prefix ...string) E
- func WithMessagef(err error, format string, args ...interface{}) E
- func WithStack(err error) E
- func Wrap(err error, message string) E
- func WrapWith(err, with error) E
- func Wrapf(err error, format string, args ...interface{}) E
- type Formatter
- type StackFormatter
- type StackTrace
Examples ¶
- Package (StackTrace)
- AllDetails
- As
- Base
- BaseWrap
- BaseWrapf
- Basef
- Cause
- Details
- Errorf
- Errorf (Wrap)
- Formatter.Format
- Formatter.MarshalJSON
- Is
- Join
- Join (Defer)
- New
- New (Printf)
- Prefix
- Prefix (Printf)
- StackFormatter.Format
- StackFormatter.Format (Width)
- StackFormatter.MarshalJSON
- UnmarshalJSON
- Unwrap
- WithDetails (Printf)
- WithMessage
- WithMessage (Printf)
- WithMessagef
- WithStack
- WithStack (Printf)
- Wrap
- Wrap (Printf)
- WrapWith
- WrapWith (Printf)
- Wrapf
Constants ¶
This section is empty.
Variables ¶
var ErrUnsupported = stderrors.ErrUnsupported
ErrUnsupported indicates that a requested operation cannot be performed, because it is unsupported. For example, a call to os.Link when using a file system that does not support hard links.
Functions and methods should not return this error but should instead return an error including appropriate context that satisfies
errors.Is(err, errors.ErrUnsupported)
either by directly wrapping ErrUnsupported or by implementing an Is method.
Functions and methods should document the cases in which an error wrapping this will be returned.
This variable is the same as the standard errors.ErrUnsupported.
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. Unwrapping stops if it encounters an error with the Cause method returning error, or Unwrap() method returning multiple errors.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { base := errors.Base("not found") err1 := errors.WithDetails(base, "file", "plans.txt") err2 := errors.WithDetails(err1, "user", "vader") fmt.Println(errors.AllDetails(err1)) fmt.Println(errors.AllDetails(err2)) }
Output: map[file:plans.txt] map[file:plans.txt user:vader]
func As ¶
As finds the first error in err's tree that matches target, and if one is found, sets target to that error value and returns true. Otherwise, it returns false.
The tree consists of err itself, followed by the errors obtained by repeatedly calling Unwrap. When err wraps multiple errors, As examines err followed by a depth-first traversal of its children.
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 ( ErrBadRequest = &MyError{400, "error"} ErrNotFound = &MyError{404, "not found"} ) func getMyErr() error { return ErrNotFound } 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 Base for a constant base error you convert to an actual error you return with WithStack or WithDetails. 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 BaseWrap when you want to create a tree 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 %s instead if you need to incorporate error's error message, but then you can also just use Basef).
Use BaseWrapf when you want to create a tree 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. %w can be provided multiple times.
Use Basef for a constant base error you convert to an actual error you return with WithStack or WithDetails. This base error you can then use in Is and As calls. Use %w format verb when you want to create a tree 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 a Cause method returning error. Otherwise, the err is unwrapped and the process is repeated. If unwrapping is not possible, Cause returns nil. Unwrapping stops if it encounters an error with Unwrap() method returning multiple errors.
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, if err's type contains a Details method returning initialized map. Otherwise, the err is unwrapped and the process is repeated. If unwrapping is not possible, Details returns nil. Unwrapping stops if it encounters an error with the Cause method returning error, or Unwrap() method returning multiple errors.
You can modify returned map to modify err's details.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { base := errors.Base("not found") err := errors.WithStack(base) errors.Details(err)["file"] = "plans.txt" errors.Details(err)["user"] = "vader" fmt.Println(errors.Details(err)) }
Output: map[file:plans.txt user:vader]
func Is ¶
Is reports whether any error in err's tree matches target.
The tree consists of err itself, followed by the errors obtained by repeatedly calling Unwrap. When err wraps multiple errors, Is examines err followed by a depth-first traversal of its children.
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. An Is method should only shallowly compare err and the target and not call Unwrap on either.
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 Unjoin ¶ added in v0.6.0
Unjoin returns the result of calling the Unwrap method on err, if err's type contains an Unwrap method returning multiple errors. Otherwise, the err is unwrapped and the process is repeated. If unwrapping is not possible, Unjoin returns nil. Unwrapping stops if it encounters an error with the Cause method returning error.
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.
Unwrap only calls a method of the form "Unwrap() error". In particular Unwrap does not unwrap errors returned by Join.
This function is a proxy for standard errors.Unwrap and is not an inverse of errors.Wrap. For that use errors.Cause.
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 and details. This is useful so that you know when you should use WithStack or WithDetails (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. (Calling WithDetails on an error with details adds an additional and independent layer of details on top of any existing details.)
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. If %w is provided multiple times, then a stack trace is always recorded.
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:165 // testing.runExample // /usr/local/go/src/testing/run_example.go:63 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1927 // main.main // _testmain.go:131 // runtime.main // /usr/local/go/src/runtime/proc.go:267 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1650 }
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:63 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1927 // main.main // _testmain.go:131 // runtime.main // /usr/local/go/src/runtime/proc.go:267 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1650 }
Output:
func Join ¶ added in v0.5.0
Join returns an error that wraps the given errors. Join also records the stack trace at the point it was called. Any nil error values are discarded. Join returns nil if errs contains no non-nil values. If there is only one non-nil value, Join behaves like WithStack on the non-nil value. The error formats as the concatenation of the strings obtained by calling the Error method of each element of errs, with a newline between each string.
Join is similar to Errorf("%w\n%w\n", err1, err2), but supports dynamic number of errors, skips nil errors, and it returns the error as-is if there is only one non-nil error already with a stack trace.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { err1 := errors.New("error1") err2 := errors.New("error2") err := errors.Join(err1, err2) fmt.Printf("% +-.1v", err) // Example output: // error1 // error2 // stack trace (most recent call first): // gitlab.com/tozd/go/errors_test.ExampleJoin // /home/user/errors/example_test.go:265 // testing.runExample // /usr/local/go/src/testing/run_example.go:63 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1927 // main.main // _testmain.go:131 // runtime.main // /usr/local/go/src/runtime/proc.go:267 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1650 // // the above error joins errors: // // error1 // stack trace (most recent call first): // gitlab.com/tozd/go/errors_test.ExampleJoin // /home/user/errors/example_test.go:263 // testing.runExample // /usr/local/go/src/testing/run_example.go:63 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1927 // main.main // _testmain.go:131 // runtime.main // /usr/local/go/src/runtime/proc.go:267 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1650 // // error2 // stack trace (most recent call first): // gitlab.com/tozd/go/errors_test.ExampleJoin // /home/user/errors/example_test.go:264 // testing.runExample // /usr/local/go/src/testing/run_example.go:63 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1927 // main.main // _testmain.go:131 // runtime.main // /usr/local/go/src/runtime/proc.go:267 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1650 }
Output:
Example (Defer) ¶
package main import ( "fmt" "os" "gitlab.com/tozd/go/errors" ) func run() (errE errors.E) { file, err := os.CreateTemp("", "test") if err != nil { return errors.WithStack(err) } defer func() { errE = errors.Join(errE, errors.WithStack(os.Remove(file.Name()))) }() // Do something with the file... return nil } func main() { errE := run() if errE != nil { fmt.Printf("error: %+v\n", errE) } else { fmt.Printf("success\n") } }
Output: success
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:63 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1927 // main.main // _testmain.go:131 // runtime.main // /usr/local/go/src/runtime/proc.go:267 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1650 }
Output:
func Prefix ¶ added in v0.7.0
Prefix annotates err with a prefix message or messages of prefix errors, wrapping prefix errors at the same time. This is similar to WithMessage but instead of using just an error message, you can provide a base error instead. 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.
If err is nil, Prefix returns nil.
Use Prefix when you want to make a new error using a base error or base errors and want to construct the new message through common prefixing. If you want to control how are messages combined, use Errorf. If you want to fully replace the message, use WrapWith.
Prefix is similar to Errorf("%w: %w", prefixErr, err), but supports dynamic number of prefix errors, skips nil errors, does not record a stack trace if err already has it, and it returns nil if err is nil.
Example ¶
package main import ( "fmt" "io" "gitlab.com/tozd/go/errors" ) func main() { err := io.EOF baseErr := errors.Base("an error") errE := errors.Prefix(err, baseErr) fmt.Println(errE) }
Output: an error: EOF
Example (Printf) ¶
package main import ( "fmt" "io" "gitlab.com/tozd/go/errors" ) func main() { err := io.EOF baseErr := errors.Base("an error") errE := errors.Prefix(err, baseErr) fmt.Printf("% +-.1v", errE) // Example output: // an error: EOF // stack trace (most recent call first): // gitlab.com/tozd/go/errors_test.ExamplePrefix_printf // /home/user/errors/example_test.go:575 // testing.runExample // /usr/local/go/src/testing/run_example.go:63 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1927 // main.main // _testmain.go:163 // runtime.main // /usr/local/go/src/runtime/proc.go:267 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1650 // // the above error joins errors: // // an error // // EOF }
Output:
func UnmarshalJSON ¶ added in v0.6.0
UnmarshalJSON unnmarshals JSON errors into placeholder errors which can then be formatted in the same way as other errors from this package.
Placeholder errors contain same data as original errors (those marshaled into JSON), but have multiple limitations:
- They do not implement stackTracer interface because addresses of stack frames are not available in JSON nor they are portable.
- Placeholder errors are not of the same type as original errors. Thus errors.Is and errors.As do not work.
- The implementation of fmt.Formatter interface of the original error is not used when formatting placeholder errors.
Placeholder errors also have different trees of wrapping errors than original errors because during JSON marshal potentially multiple levels of wrapping are combined into one JSON object. Nested objects happen only for errors implementing causer or unwrapper interface returning multiple errors.
Example ¶
package main import ( "encoding/json" "fmt" "gitlab.com/tozd/go/errors" ) func main() { base := errors.Base("not found") errE := errors.Wrap(base, "image not found") errors.Details(errE)["filename"] = "star.png" data, err := json.Marshal(errE) if err != nil { panic(err) } errFromJSON, errE := errors.UnmarshalJSON(data) if errE != nil { panic(errE) } fmt.Printf("% #+-.1v", errFromJSON) // Example output: // image not found // filename=star.png // stack trace (most recent call first): // gitlab.com/tozd/go/errors_test.ExampleUnmarshalJSON // /home/user/errors/example_test.go:486 // testing.runExample // /usr/local/go/src/testing/run_example.go:63 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1927 // main.main // _testmain.go:145 // runtime.main // /usr/local/go/src/runtime/proc.go:267 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1650 // // the above error was caused by the following error: // // not found }
Output:
func WithDetails ¶ added in v0.4.0
WithDetails wraps err into an error which implements the detailer interface to access a map with optional additional details 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 WithDetails when you have an err which implements stackTracer interface but does not implement detailer interface as well. You can also use WithStack for that but you cannot provide initial details using WithStack like you can with WithDetails.
It is also 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 always wraps err and adds an additional and independent layer of details on top of any existing details.
You can provide initial details by providing pairs of keys (strings) and values (interface{}).
Example (Printf) ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { base := errors.Base("not found") err := errors.WithDetails(base, "file", "plans.txt", "user", "vader") fmt.Printf("%#v", err) }
Output: not found file=plans.txt user=vader
func WithMessage ¶
WithMessage annotates err with a prefix message or messages. 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.
If err is nil, WithMessage returns nil.
WithMessage is similar to Errorf("%s: %w", prefix, err), but supports dynamic number of prefixes, and it returns nil if err is 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:63 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1927 // main.main // _testmain.go:131 // runtime.main // /usr/local/go/src/runtime/proc.go:267 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1650 }
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.
If err is nil, WithMessagef returns nil.
WithMessagef is similar to Errorf(format + ": %w", args..., err), but it returns nil if err is 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.
Use WithStack 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.
You can also use WithStack when you have an err which implements stackTracer interface but does not implement detailer interface as well, but you cannot provide initial details like you can with WithDetails.
WithStack is similar to Errorf("%w", err), but returns err as-is if err already satisfies interface E and has a stack trace, and it returns nil if err is nil.
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:85 // testing.runExample // /usr/local/go/src/testing/run_example.go:63 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1927 // main.main // _testmain.go:131 // runtime.main // /usr/local/go/src/runtime/proc.go:267 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1650 }
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.
Use Wrap when you want to make a new error with a different error message, while preserving the cause of the new error. If you want to reuse the err error message use WithMessage or Errorf instead.
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("% +-.1v", 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:116 // testing.runExample // /usr/local/go/src/testing/run_example.go:63 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1927 // main.main // _testmain.go:131 // runtime.main // /usr/local/go/src/runtime/proc.go:267 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1650 // // 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:115 // testing.runExample // /usr/local/go/src/testing/run_example.go:63 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1927 // main.main // _testmain.go:131 // runtime.main // /usr/local/go/src/runtime/proc.go:267 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1650 }
Output:
func WrapWith ¶ added in v0.6.0
WrapWith makes the "err" error the cause of the "with" error. This is similar to Wrap but instead of using just an error message, you can provide a base error instead. If "err" is nil, WrapWith returns nil. If "with" is nil, WrapWith panics.
If the "with" error does already have a stack trace, a stack trace is recorded at the point WrapWith was called.
The new error wraps two errors, the "with" error and the "err" error, making it possible to use both Is and As on the new error to traverse both "with" and "err" errors at the same time.
Note that the new error introduces a new context for details so any details from the "err" and "with" errors are not available through AllDetails on the new error.
Use WrapWith when you want to make a new error using a base error with a different error message, while preserving the cause of the new error. If you want to reuse the "err" error message use Prefix or Errorf instead.
Example ¶
package main import ( "fmt" "io" "gitlab.com/tozd/go/errors" ) func main() { cause := io.EOF baseErr := errors.Base("an error") err := errors.WrapWith(cause, baseErr) fmt.Println(err) }
Output: an error
Example (Printf) ¶
package main import ( "fmt" "io" "gitlab.com/tozd/go/errors" ) func main() { cause := io.EOF baseErr := errors.Base("an error") err := errors.WrapWith(cause, baseErr) fmt.Printf("% +-.1v", err) // Example output: // an error // stack trace (most recent call first): // gitlab.com/tozd/go/errors_test.ExampleWrapWith_printf // /home/user/errors/example_test.go:536 // testing.runExample // /usr/local/go/src/testing/run_example.go:63 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1927 // main.main // _testmain.go:155 // runtime.main // /usr/local/go/src/runtime/proc.go:267 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1650 // // the above error was caused by the following error: // // EOF }
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 (use %s instead if you need to incorporate cause's error message). If err is nil, Wrapf returns nil.
Use Wrapf when you want to make a new error with a different error message, preserving the cause of the new error. If you want to reuse the err error message use WithMessage or Errorf instead.
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
type Formatter ¶ added in v0.6.0
type Formatter struct { Error error // Provide a function to obtain the error's message. // By default error's Error() is called. GetMessage func(error) string `exhaustruct:"optional"` }
Formatter formats an error as text and marshals the error as JSON.
func (Formatter) Format ¶ added in v0.6.0
Format formats the error as text according to the fmt.Formatter interface.
The error does not have to necessary come from this package and it will be formatted in the same way if it implements interfaces used by this package (e.g., stackTracer or detailer interfaces). By default, only if those interfaces are not implemented, but fmt.Formatter interface is, formatting will be delegated to the error itself. You can change this default through format precision.
Errors which do come from this package can be directly formatted by the fmt package in the same way as this function does as they implement fmt.Formatter interface. If you are not sure about the source of the error, it is safe to call this function on them as well.
The following verbs are supported:
%s the error message %q the quoted error message %v by default the same as %s
You can control how is %v formatted through the width and precision arguments and flags. The width argument controls the width of the indent step in spaces. The default (no width argument) indents with a tab step. Width is passed through to the stack trace formatting.
The following flags for %v are supported:
'#' list details as key=value lines after the error message, when available '+' follow with the %+v formatted stack trace, if available '-' add human friendly messages to delimit parts of the text ' ' add extra newlines to separate parts of the text better
Precision is specified by a period followed by a decimal number and enable modes of operation. The following modes are supported:
.0 do not change default behavior, this is the default .1 recurse into error causes and joined errors .2 prefer error's fmt.Formatter interface implementation if error implements it .3 recurse into error causes and joined errors, but prefer fmt.Formatter interface implementation if any error implements it; this means that recursion stops if error's formatter does not recurse
When any flag or non-zero precision mode is used, it is assured that the text ends with a newline, if it does not already do so.
Example ¶
package main import ( "fmt" "gitlab.com/tozd/go/errors" ) func main() { base := errors.Base("not found") err := errors.Wrap(base, "image not found") errors.Details(err)["filename"] = "star.png" fmt.Printf("% #+-.1v", err) // Example output: // image not found // filename=star.png // stack trace (most recent call first): // gitlab.com/tozd/go/errors_test.ExampleFormatter_Format // /home/user/errors/example_test.go:395 // testing.runExample // /usr/local/go/src/testing/run_example.go:63 // testing.runExamples // /usr/local/go/src/testing/example.go:44 // testing.(*M).Run // /usr/local/go/src/testing/testing.go:1927 // main.main // _testmain.go:137 // runtime.main // /usr/local/go/src/runtime/proc.go:267 // runtime.goexit // /usr/local/go/src/runtime/asm_amd64.s:1650 // // the above error was caused by the following error: // // not found }
Output:
func (Formatter) MarshalJSON ¶ added in v0.6.0
MarshalJSON marshals the error as JSON according to the json.Marshaler interface.
The error does not have to necessary come from this package and it will be marshaled in the same way if it implements interfaces used by this package (e.g., stackTracer or detailer interfaces). Only if those interfaces are not implemented, but json.Marshaler interface is or the error is a struct with JSON struct tags, marshaling will be delegated to the error itself.
Errors which do come from this package can be directly marshaled in the same way as this function does as they implement json.Marshaler interface. If you are not sure about the source of the error, it is safe to call this function on them as well.
Example ¶
package main import ( "bytes" "encoding/json" "os" "gitlab.com/tozd/go/errors" ) func main() { base := errors.Base("not found") errE := errors.Wrap(base, "image not found") errors.Details(errE)["filename"] = "star.png" data, err := json.Marshal(errE) if err != nil { panic(err) } out := new(bytes.Buffer) _ = json.Indent(out, data, "", "\t") _, _ = out.WriteTo(os.Stdout) // Example output: // { // "cause": { // "error": "not found" // }, // "error": "image not found", // "filename": "star.png", // "stack": [ // { // "name": "gitlab.com/tozd/go/errors_test.ExampleFormatter_MarshalJSON", // "file": "/home/user/errors/example_test.go", // "line": 427 // }, // { // "name": "testing.runExample", // "file": "/usr/local/go/src/testing/run_example.go", // "line": 63 // }, // { // "name": "testing.runExamples", // "file": "/usr/local/go/src/testing/example.go", // "line": 44 // }, // { // "name": "testing.(*M).Run", // "file": "/usr/local/go/src/testing/testing.go", // "line": 1927 // }, // { // "name": "main.main", // "file": "_testmain.go", // "line": 145 // }, // { // "name": "runtime.main", // "file": "/usr/local/go/src/runtime/proc.go", // "line": 267 // }, // { // "name": "runtime.goexit", // "file": "/usr/local/go/src/runtime/asm_amd64.s", // "line": 1650 // } // ] // } }
Output:
type StackFormatter ¶ added in v0.6.0
type StackFormatter struct {
Stack []uintptr
}
StackFormatter formats a stack trace as text and marshals the stack trace as JSON.
func (StackFormatter) Format ¶ added in v0.6.0
func (s StackFormatter) Format(st fmt.State, verb rune)
Format formats the stack of frames as text according to the fmt.Formatter interface.
The stack trace can come from errors in this package, from runtime.Callers, or from somewhere else.
Each frame in the stack is formatted according to the format and is ended by a newline.
The following verbs are supported:
%s lists the source file basename %d lists the source line number %n lists the short function name %v equivalent to %s:%d
StackFormat accepts flags that alter the formatting of some verbs, as follows:
%+s lists the full function name and full compile-time path of the source file, separated by \n\t (<funcname>\n\t<path>) %+v lists the full function name and full compile-time path of the source file with the source line number, separated by \n\t (<funcname>\n\t<path>:<line>)
StackFormat also accepts the width argument which controls the width of the indent step in spaces. The default (no width argument) indents with a tab step.
Example ¶
package main import ( "fmt" "runtime" "gitlab.com/tozd/go/errors" ) func main() { const depth = 1 var cs [depth]uintptr runtime.Callers(1, cs[:]) fmt.Printf("%+v", errors.StackFormatter{cs[:]}) // Example output: // gitlab.com/tozd/go/errors_test.ExampleStackFormatter_Format // /home/user/errors/example_test.go:374 }
Output:
Example (Width) ¶
package main import ( "fmt" "runtime" "gitlab.com/tozd/go/errors" ) func main() { const depth = 1 var cs [depth]uintptr runtime.Callers(1, cs[:]) fmt.Printf("%+2v", errors.StackFormatter{cs[:]}) // Example output: // gitlab.com/tozd/go/errors_test.ExampleStackFormatter_Format // /home/user/errors/example_test.go:385 }
Output:
func (StackFormatter) MarshalJSON ¶ added in v0.6.0
func (s StackFormatter) MarshalJSON() ([]byte, error)
MarshalJSON marshals the stack of frames as JSON according to the json.Marshaler interface.
JSON consists of an array of frame objects, each with (function) name, file (name), and line fields.
Example ¶
package main import ( "encoding/json" "fmt" "runtime" "gitlab.com/tozd/go/errors" ) func main() { const depth = 1 var cs [depth]uintptr runtime.Callers(1, cs[:]) data, err := json.Marshal(errors.StackFormatter{cs[:]}) if err != nil { panic(err) } fmt.Println(string(data)) // Example output: // [{"name":"gitlab.com/tozd/go/errors_test.ExampleStackFormatter_MarshalJSON","file":"/home/user/errors/example_test.go","line":360}] }
Output:
type StackTrace ¶ added in v0.6.0
type StackTrace = []uintptr
StackTrace is a type alias for better compatibility with github.com/pkg/errors. It does not define a new type.