Documentation ¶
Overview ¶
Package errors is an augmented replacement package for the stdlib "errors" package. It contains the same New method, but also has some handy methods and types for dealing with errors.
Index ¶
- Constants
- Variables
- func Any(err error, fn func(error) bool) (any bool)
- func Append(errs ...error) error
- func As(e error, target any) bool
- func Contains(outer error, inner error) bool
- func Filter(err error, exclude error, others ...error) error
- func FilterFunc(err error, shouldFilter func(error) bool) error
- func Flatten(err error) error
- func GetTags(err error) map[TagKey]any
- func Is(e error, target error) bool
- func IsPanicking(skip int) bool
- func Join(errs ...error) error
- func Log(ctx context.Context, err error, excludePkgs ...string)
- func New(msg string, tags ...TagValueGenerator) error
- func RenderStack(err error, excludePkgs ...string) []string
- func SingleError(err error) error
- func TagValueIn(t TagKey, err error) (value any, ok bool)
- func Unwrap(err error) error
- func Walk(err error, fn func(error) bool)
- func WalkLeaves(err error, fn func(error) bool)
- type Annotator
- type BoolTag
- type LazyMultiError
- type MultiError
- type TagKey
- type TagValue
- type TagValueGenerator
- type Wrapped
Examples ¶
Constants ¶
const (
// RenderedStackDumpRe is coupled with renderedStackDumpFormat to indicate its regex expression.
RenderedStackDumpRe = `goroutine \d+:\n`
)
Variables ¶
var ErrUnsupported = errors.ErrUnsupported
ErrUnsupported re-exports errors.ErrUnsupported from the standard library.
Functions ¶
func Any ¶
Any performs a Walk traversal of an error, returning true (and short-circuiting) if the supplied filter function returns true for any visited error.
If err is nil, Any will return false.
func Append ¶
Append takes a list of errors, whether they are nil, MultiErrors, or regular errors, and combines them into a single error.
The resulting error is not a multierror unless it needs to be.
Sample usage shown below:
err := DoSomething() err = errors.Append(err, DoSomethingElse()) err = errors.Append(err, DoAThirdThing()) if err != nil { // log an error or something, I don't know } // proceed as normal
func Contains ¶
Contains performs a Walk traversal of |outer|, returning true if any visited error is equal to |inner|.
func Filter ¶
Filter examines a supplied error and removes instances of excluded errors from it. If the entire supplied error is excluded, Filter will return nil.
If a MultiError is supplied to Filter, it will be recursively traversed, and its child errors will be turned into nil if they match the supplied filter. If a MultiError has all of its children converted to nil as a result of the filter, it will itself be reduced to nil.
func FilterFunc ¶
FilterFunc examines a supplied error and removes instances of errors that match the supplied filter function. If the entire supplied error is removed, FilterFunc will return nil.
If a MultiError is supplied to FilterFunc, it will be recursively traversed, and its child errors will be turned into nil if they match the supplied filter function. If a MultiError has all of its children converted to nil as a result of the filter, it will itself be reduced to nil.
Consqeuently, if err is a MultiError, shouldFilter will be called once with err as its value and once for every non-nil error that it contains.
func Flatten ¶
Flatten collapses a multi-dimensional MultiError space into a flat MultiError, removing "nil" errors.
If err is not an errors.MultiError, will return err directly.
As a special case, if merr contains no non-nil errors, nil will be returned.
func GetTags ¶
GetTags returns a map of all TagKeys set in this error to their value.
A nil value means that the tag is present, but has a nil associated value.
This is done in a depth-first traversal of the error stack, with the most-recently-set value of the tag taking precedence.
func IsPanicking ¶
IsPanicking returns true iff the current goroutine is panicking.
Always returns false when not invoked via a defer'd function.
This should only be used to indicate some best-effort error status, not to modify control flow of the program. Panics are still crashes!
HACK: Detection is implemented by looking up the stack at most skip+10 frames above IsPanicking to find if the golang panic handler is on the stack. This may break when the Go runtime changes!
`skip` indicates how many additional frames of the stack to skip (a value of 0 starts the stack at the caller of `IsPanicking`). Clamps to a minimum value of 0.
Does NOT invoke `recover()`. WILL detect `panic(nil)`.
Example ¶
package main import ( "fmt" "runtime/debug" "strings" ) func CrashingFunction() { panic("boom") } func main() { A := func(crash bool) { defer func() { if IsPanicking(0) { fmt.Println("PANIK!") } else { fmt.Println("kalm") } }() if crash { fmt.Println("about to boom") CrashingFunction() } else { fmt.Println("smooth sailing") } } defer func() { // Make sure IsPanicking didn't do a `recover()`, which would goof up the // stack. stack := string(debug.Stack()) if !strings.Contains(stack, "CrashingFunction") { fmt.Println("stack trace doesn't originate from CrashingFunction") } else { fmt.Println("stack trace originates from CrashingFunction") } // But recover ourselves to make sure ExampleIsPanicking actually passes // instead of crashing with an unrecovered panic. recover() }() if IsPanicking(0) { fmt.Println("cannot be panicing when not in defer'd function.") } A(false) fmt.Println("first pass success") A(true) }
Output: smooth sailing kalm first pass success about to boom PANIK! stack trace originates from CrashingFunction
func Log ¶
Log logs the full error. If this is an Annotated error, it will log the full stack information as well.
This is a shortcut for logging the output of RenderStack(err).
If resulting log message is large, splits it into log entries of at most 64KiB.
func New ¶
func New(msg string, tags ...TagValueGenerator) error
New is an API-compatible version of the standard errors.New function. Unlike the stdlib errors.New, this will capture the current stack information at the place this error was created.
func RenderStack ¶
RenderStack renders the error to a list of lines.
func SingleError ¶
SingleError provides a simple way to uwrap a MultiError if you know that it could only ever contain one element.
If err is a MultiError, return its first element. Otherwise, return err.
func TagValueIn ¶
TagValueIn will retrieve the tagged value from the error that's associated with this key, and a boolean indicating if the tag was present or not.
func Unwrap ¶
Unwrap unwraps a wrapped error recursively, returning its inner error.
If the supplied error is not nil, Unwrap will never return nil. If a wrapped error reports that its Unwrap is nil, that error will be returned.
func Walk ¶
Walk performs a depth-first traversal of the supplied error, unfolding it and invoke the supplied callback for each layered error recursively. If the callback returns true, Walk will continue its traversal.
- If walk encounters a MultiError, the callback is called once for the outer MultiError, then once for each inner error.
- If walk encounters a Wrapped error, the callback is called for the outer and inner error.
- If an inner error is, itself, a container, Walk will recurse into it.
If err is nil, the callback will not be invoked.
func WalkLeaves ¶
WalkLeaves is like Walk, but only calls fn on leaf nodes.
Types ¶
type Annotator ¶
type Annotator struct {
// contains filtered or unexported fields
}
Annotator is a builder for annotating errors. Obtain one by calling Annotate on an existing error or using Reason.
See the example test for Annotate to see how this is meant to be used.
func Annotate ¶
Annotate captures the current stack frame and returns a new annotatable error, attaching the publicly readable `reason` format string to the error. You can add additional metadata to this error with the 'InternalReason' and 'Tag' methods, and then obtain a real `error` with the Err() function.
If this is passed nil, it will return a no-op Annotator whose .Err() function will also return nil.
The original error may be recovered by using Wrapped.Unwrap on the returned error.
Rendering the derived error with Error() will render a summary version of all the public `reason`s as well as the initial underlying error's Error() text. It is intended that the initial underlying error and all annotated reasons only contain user-visible information, so that the accumulated error may be returned to the user without worrying about leakage.
You should assume that end-users (including unauthenticated end users) may see the text in the `reason` field here. To only attach an internal reason, leave the `reason` argument blank and don't pass any additional formatting arguments.
The `reason` string is formatted with `args` and may contain Sprintf-style formatting directives.
Example ¶
package main import ( "fmt" ) func someProcessingFunction(val int) error { if val == 1 { // New and Reason automatically include stack information. return Reason("bad number: %d", val).Err() } if err := someProcessingFunction(val - 1); err != nil { // correctly handles recursion return Annotate(err, "").InternalReason("val(%d)", val).Err() } return nil } func someLibFunc(vals ...int) error { for i, v := range vals { if err := someProcessingFunction(v); err != nil { return Annotate(err, "processing %d", v). InternalReason("secret(%s)/i(%d)", "value", i).Err() } } return nil } type MiscWrappedError struct{ error } func (e *MiscWrappedError) Error() string { return fmt.Sprintf("super wrapper(%s)", e.error.Error()) } func (e *MiscWrappedError) Unwrap() error { return e.error } func errorWrapper(err error) error { if err != nil { err = &MiscWrappedError{err} } return err } func someIntermediateFunc(vals ...int) error { errch := make(chan error) go func() { defer close(errch) errch <- Annotate(errorWrapper(someLibFunc(vals...)), "could not process").Err() }() me := MultiError(nil) for err := range errch { if err != nil { me = append(me, err) } } if me != nil { return Annotate(me, "while processing %v", vals).Err() } return nil } func main() { if err := someIntermediateFunc(3); err != nil { err = Annotate(err, "top level").Err() fmt.Println("Public-facing error:\n ", err) fmt.Println("\nfull error:") for _, l := range FixForTest(RenderStack(err, "runtime", "_test")) { fmt.Println(l) } } }
Output: Public-facing error: top level: while processing [3]: could not process: super wrapper(processing 3: bad number: 1) full error: original error: bad number: 1 GOROUTINE LINE #? go.chromium.org/luci/common/errors/annotate_example_test.go:24 - errors.someProcessingFunction() reason: bad number: 1 #? go.chromium.org/luci/common/errors/annotate_example_test.go:26 - errors.someProcessingFunction() internal reason: val(2) #? go.chromium.org/luci/common/errors/annotate_example_test.go:26 - errors.someProcessingFunction() internal reason: val(3) From frame 2 to 3, the following wrappers were found: unknown wrapper *errors.MiscWrappedError #? go.chromium.org/luci/common/errors/annotate_example_test.go:35 - errors.someLibFunc() reason: processing 3 internal reason: secret(value)/i(0) From frame 3 to 4, the following wrappers were found: internal reason: MultiError 1/1: following first non-nil error. #? go.chromium.org/luci/common/errors/annotate_example_test.go:59 - errors.someIntermediateFunc.func1() reason: could not process ... skipped SOME frames in pkg "runtime"... GOROUTINE LINE #? go.chromium.org/luci/common/errors/annotate_example_test.go:68 - errors.someIntermediateFunc() reason: while processing [3] #? go.chromium.org/luci/common/errors/annotate_example_test.go:74 - errors.ExampleAnnotate() reason: top level #? testing/run_example.go:XXX - testing.runExample() #? testing/example.go:XXX - testing.runExamples() #? testing/testing.go:XXX - testing.(*M).Run() #? ./_testmain.go:XXX - main.main() ... skipped SOME frames in pkg "runtime"...
func Reason ¶
Reason builds a new Annotator starting with reason. This allows you to use all the formatting directives you would normally use with Annotate, in case your originating error needs tags or an internal reason.
errors.Reason("something bad: %d", value).Tag(transient.Tag).Err()
Prefer this form to errors.New(fmt.Sprintf("...")) or fmt.Errorf("...")
func (*Annotator) InternalReason ¶
InternalReason adds a stack-trace-only internal reason string (for humans) to this error.
The text here will only be visible when using `errors.Log` or `errors.RenderStack`, not when calling the .Error() method of the resulting error.
The `reason` string is formatted with `args` and may contain Sprintf-style formatting directives.
func (*Annotator) Tag ¶
func (a *Annotator) Tag(tags ...TagValueGenerator) *Annotator
Tag adds a tag with an optional value to this error.
`value` is a unary optional argument, and must be a simple type (i.e. has a reflect.Kind which is a base data type like bool, string, or int).
type BoolTag ¶
type BoolTag struct{ Key TagKey }
BoolTag is an error tag implementation which holds a boolean value.
It should be constructed like:
var myTag = errors.BoolTag{Key: errors.NewTagKey("some description")}
func (BoolTag) GenerateErrorTagValue ¶
GenerateErrorTagValue implements TagValueGenerator, and returns a default value for the tag of `true`. If you want to set this BoolTag value to false, use BoolTag.Off().
type LazyMultiError ¶
type LazyMultiError interface { // Assign semantically assigns the error to the given index in the MultiError. // If the error is nil, no action is taken. Otherwise the MultiError is // allocated to its full size (if not already), and the error assigned into // it. // // Returns true iff err != nil (i.e. "was it assigned?"), so you can use this // like: // if !lme.Assign(i, err) { // // stuff requiring err == nil // } Assign(int, error) bool // GetOne returns the error at the given index (which may be nil) GetOne(int) error // Get returns the MultiError, or nil, if no non-nil error was Assign'd. Get() error }
LazyMultiError is a lazily-constructed MultiError.
LazyMultiError is like MultiError, except that you know the ultimate size up front, and then you call Assign for each error encountered, and it's potential index. The underlying MultiError will only be allocated if one of the Assign'd errors is non-nil. Similarly, Get will retrieve either the allocated MultiError, or nil if no error was encountered. Build one with NewLazyMultiError.
func NewLazyMultiError ¶
func NewLazyMultiError(size int) LazyMultiError
NewLazyMultiError makes a new LazyMultiError of the provided size.
type MultiError ¶
type MultiError []error
MultiError is a simple `error` implementation which represents multiple `error` objects in one.
func NewMultiError ¶
func NewMultiError(errors ...error) MultiError
NewMultiError create new multi error from given errors.
Can be used to workaround 'go vet' confusion "composite literal uses unkeyed fields" or if you do not want to remember that MultiError is in fact []error.
func (MultiError) AsError ¶
func (m MultiError) AsError() error
AsError returns an `error` interface for this MultiError only if it has >0 length.
func (MultiError) Error ¶
func (m MultiError) Error() string
func (MultiError) Is ¶
func (m MultiError) Is(other error) bool
Is implements errors.Is.
This implementation does not interfere with errors.Is, but DOES allow matching, especially in tests, when matching a MultiError pattern against another MultiError whose size matches exactly and whose contents recursively match with `errors.Is`.
This is necessary because the stdlib `errors.Is` will call `Unwrap() []error` on the source `err`, but NOT the target `err`, meaning that without this method, MultiError can NEVER be the right hand side of a successful `errors.Is` call.
If this returns false, `errors.Is` will continue its typical algorithm.
func (*MultiError) MaybeAdd ¶
func (m *MultiError) MaybeAdd(err error)
MaybeAdd will add `err` to `m` if `err` is not nil.
func (MultiError) Summary ¶
func (m MultiError) Summary() (n int, first error)
Summary gets the total count of non-nil errors and returns the first one.
func (MultiError) Unwrap ¶
func (m MultiError) Unwrap() []error
Unwrap turns MultiError to a slices of errors.
This will make MultiError works with errors.Is or errors.As from stdlib.
type TagKey ¶
type TagKey *tagDescription
TagKey objects are used for applying tags and finding tags/values in errors. See NewTag for details.
func NewTagKey ¶
NewTagKey creates a new TagKey.
Use this with a BoolTag or your own custom tag implementation.
Example (bool tag):
var myTag = errors.BoolTag{Key: errors.NewTagKey("this error is a user error")} err = myTag.Apply(err) myTag.In(err) // == true err2 := myTag.Off().Apply(err) myTag.In(err2) // == false
Example (custom tag)
type SomeType int type myTag struct { Key errors.TagKey } func (m myTag) With(value SomeType) errors.TagValue { return errors.TagValue{Key: m.Key, Value: value} } func (m myTag) In(err error) (v SomeType, ok bool) { d, ok := errors.TagValueIn(m.Key, err) if ok { v = d.(SomeType) } return } var MyTag = myTag{errors.NewTagKey("has a SomeType")}
You could then use it like:
err = MyTag.With(100).Apply(err) MyTag.In(err) // == true errors.ValueIn(err) // == (SomeType(100), true)
type TagValue ¶
TagValue represents a (tag, value) to be used with Annotate.Tag, or may be applied to an error directly with the Apply method.
Usually tag implementations will have a typesafe With method that generates these. Avoid constructing these ad-hoc so that a given tag definition can control the type safety around these.
func (TagValue) Apply ¶
Apply applies this tag value (key+value) directly to the error. This is a shortcut for `errors.Annotate(err, "").Tag(t).Err()`.
func (TagValue) GenerateErrorTagValue ¶
GenerateErrorTagValue implements TagValueGenerator
type TagValueGenerator ¶
type TagValueGenerator interface {
GenerateErrorTagValue() TagValue
}
TagValueGenerator generates (TagKey, value) pairs, for use with Annoatator.Tag and New().