Documentation ¶
Overview ¶
Package errors provides utilities for working with Go errors. It is meant to work as a drop-in replacement for the standard library https://go.pkg.dev/errors, and is based on:
• https://github.com/pkg/errors,
• https://pkg.go.dev/golang.org/x/xerrors, and
• https://github.com/uber-go/multierr.
Error context ¶
When we write the following:
if err != nil { return err }
... we allow errors to lose error context (ie human-readable root cause and debugging information).
Go 1.13 introduced "error wrapping," where we can add context messages like this:
if err != nil { return fmt.Errorf("contextual information: %w", err) }
This helps us identify a root cause, and place that cause in some program context recursively.
However, we are not always in control of the full extent of our codebase, and even when we are we don't always write code that provides useful error context.
In addition, there are cases where we want more or different information appended to an error: caller frame locations and stacks, easily identifiable and immutable causes, or collections of errors that result from coalescing the outcome of multiple tasks for example.
This package allows users:
1. to add more fine-grained contextual information to their errors;
2. to chain or group errors and extract their contextual information;
3. to format errors with all of their context when printing them; and
4. to retain a simple API for most use cases while retaining the ability to directly interact with, or tune, error chains and groups.
Error wrapping in Go 1.13 ¶
This package is meant to be used with error wrapping.
Importantly: this package does not attempt to replace this system. Instead, errors is meant to enrich it: all the types and interfaces here work with Is, As, and Unwrap; using fmt.Errorf is also supported. In fact, this package re-exports New, Is, As, and Unwrap so that you don't need to import the standard library's "errors" package as well:
import "github.com/secureworks/errors" var Err = errors.New("example err") // Same as if we had used: import "errors"
Wrapping multiple errors in Go 1.20 ¶
This package is meant to be used with the multiple error wrapping support introduced in Go 1.20.
Stack traces or call frames ¶
For debugging context this package provides the errors.Frame interface and errors.Frames type. Frame is based on the runtime.Frame and xerrors.Frame types (https://pkg.go.dev/golang.org/x/xerrors#Frame) and defines one method, Location:
type Frame interface { Location() (function string, file string, line int) }
You can create a Frame in your code directly with errors.Caller or errors.CallerAt. You can also use the runtime package to acquire a "program counter" (https://pkg.go.dev/runtime#Frame) using errors.FrameFromPC. Finally, you can generate a "synthetic" frame by passing the constituent data directly to errors.NewFrame:
fr := errors.Caller() // Same as errors.CallerAt(0) fr.Location() // ... returns "pkg/function.name", "file_name.go", 20
errors.Frames is a slice of error.Frame. You can bunch together a group of errors.Frame instances to create this list, or you can use errors.CallStack or errors.CallStackAt to get the entire call stack (or some subset of it).
stack := errors.CallStack() // Same as errors.CallStackAt(0) stack[0].Location() // ... returns "pkg/function.name", "file_name.go", 20
These two approaches to building errors.Frames can be described, from the point of view of adding error context, as "appending frames" or as "attaching a stack trace." Which you want to do depends on your use case: do you want targeted caller references or an entire stack trace attached to your error? The "stack trace" approach is the only one supported by https://github.com/pkg/errors, while the "append frames" approach is supported by https://pkg.go.dev/golang.org/x/xerrors, as examples.
Since the latter approach (appending frames) leads to more compact and efficient debugging information, and since it mirrors the Go idiom of recursively building an error context, this package prefers its use and includes the errors.Errorf function to that effect. Using stack traces is fully supported, however, and errors.FramesFrom will extract a stack trace, even if there are appended frames in an error chain (if both are available), in order to avoid context loss.
Wrapping Errors ¶
This package provides functions for adding context to an error with a group of "error wrappers" that build an "error chain" of values that recursively add that context to some base error. The error wrappers it provides are:
// Attach a stack trace starting at the current caller. err := errors.WithStackTrace(err) // Append a frame for the current caller. err = errors.WithFrame(err) // Appends a frame for the caller *n* steps up the chain (in this case, 1). err = errors.WithFrameAt(err, 1)
These wrappers are accompanied by versions that create a new error and immediately wrap it: errors.NewWithStackTrace, errors.NewWithFrame, and errors.NewWithFrameAt.
A final helper, errors.Errorf, is provided to allow for the common idiom:
err := errors.WithFrame(fmt.Errorf("message context: %w", err)) // ... the same as: // err := errors.Errorf("message context: %w", err)
In order to ensure the user correctly structures errors.Errorf, the function will panic if you are not wrapping an error with the "%w" verb.
Multierrors ¶
Wrapping errors is useful enough, but there are instances when we want to merge multiple errors and handle them as a group, eg: a "singular" result of running multiple tasks, handling a response to some graph resolution where each path may include a separate error, returning some possible error *and* the coalesced result of running a deferred function, etc.
This can be handled with a simple []error slice, but that can be frustrating since so many libraries, codebases and standards expect that well-formatted code adheres to the Go idiom of returning a single error value from a function or providing an error chain to errors.Unwrap.
To solve this the package provides a type errors.MultiError that wraps a slice of errors and implements the error interface (and others: errors.As, errors.Is, fmt.Formatter, et al).
It also provides helper functions for writing code that handles multierrors either as their own type or as a basic error type. For example, if you want to merge the results of two functions into a multierror, you can use:
func actionWrapper() *errors.MultiError { err1 := actionA() err2 := actionB() return errors.NewMultiError(err1, err2) } if merr := actionWrapper(); merr != nil { fmt.Printf("%+v\n", merr.Unwrap()) }
Retrieving error information ¶
The additional types of context this package's wrappers add: call frames or stack (for debugging) can most easily be extracted from an error or error chain using errors.FramesFrom and errors.ErrorsFrom.
errors.FramesFrom returns an errors.Frames slice. It identifies if the error chain has a stack trace, and if it does it will return the oldest / deepest one available (to get the most context). If the error chain does not have a stack trace, but has frames appended, errors.FramesFrom merges those frames in order from most recent to oldest and returns it.
err := errors.NewWithStackTrace("err") frames := errors.FramesFrom(err) len(frames) // 6 err := errors.NewWithFrame("err") err = errors.WithFrame(err) frames := errors.FramesFrom(err) len(frames) // 2 err := errors.New("err") frames := errors.FramesFrom(err) len(frames) // 0
errors.ErrorsFrom returns a slice of errors, unwrapping the first multierror found in an error chain and returning the results. If none is found, the slice of errors contains the given error, or is nil if the error is nil:
merr := errors.NewMultiError(errors.New("err"), errors.New("err")) err := errors.WithStackTrace(merr) errs := errors.ErrorsFrom(err) len(errs) // 2
Masking Errors ¶
Because this errors package allows us to add a fair amount of sensitive context to errors, and since Go errors are often used to provide end users with useful information, it is important to also provide primitives for removing (or "masking") context in an error chain.
Foremost is the wrapper function errors.WithMessage, which will reset a message context (often including information that is logged or that will be provided to an end user), while leaving the rest of the context and type information available on the error chain to be used by calling code. For example:
err := errors.NewWithFrame("user 4356789 missing role Admin: has roles [EndUser] in tenant 42") // ... err = errors.WithMessage(err, "user unauthorized") fmt.Printf("%+v", err) //> user unauthorized //> pkg/function.name //> file_name.go:20
The resulting error can be unwrapped:
err := errors.Unwrap(err) fmt.Print(err.Error()) //> user 4356789 missing role Admin: has roles [EndUser] in tenant 42
The opposite effect can be had by using errors.Mask to remove all non-message context:
err := errors.NewWithFrame("user unauthorized") // ... err = errors.Mask(err) fmt.Printf("%+v", err) //> user unauthorized errors.FramesFrom(err) // []
While errors.Mask removes all context, errors.Opaque retains all context but squashes the error chain so that type information, or any context that is not understood by this errors package is removed. This can be useful to ensure errors do not wrap some context from an outside library not under the calling code's control.
Formatted printing of errors ¶
All error values returned from this package implement fmt.Formatter and can be formatted by the fmt package. The following verbs are supported:
%s print the error's message context. Frames are not included in the message. %v see %s %+v extended format. Each Frame of the error's Frames will be printed in detail.
This not an exhaustive list, see the tests for more.
Unexported interfaces ¶
Following the precedent of other errors packages, this package is implemented using a series of unexported structs that all conform to various interfaces in order to be activated. All the error wrappers, for example, implement the error interface face type and the unwrap interface:
interface { Error() string // The built-in language type "error." Unwrap() error // The unexported standard library interface used to unwrap errors. }
This package does export the important interface errors.Frame, but otherwise it does not export interfaces that are not necessary to use the library. However, if you want to write more complex code that makes use of, or augments, this package, there are unexported interfaces throughout that can be used to work more directly with its types. These include:
interface { // Used for extracting context. Frames() errors.Frames // Ie "framer," the interface for getting any frames from an error. // Used to distinguish a stack trace from other appended frames: StackTrace() []uintptr // Ie "stackTracer," the interface for getting a local stack trace from an error. // Used to distinguish a frame that was generated from runtime (instead of synthetically): PC() uintptr // Ie "programCounter," the interface for getting a frame's program counter. // Used to identify an error that coalesces multiple errors: Unwrap() []error // Ie "multierror," the interface for getting multiple merged errors. }
Though none of these are exported by this package, they are considered a part of its stable public interface.
Example (DebugTasks) ¶
By using errors.Errorf and errors.WithFrame we can add useful debugging information and error context to go routines, which can be hard to track down.
We can also coalesce errors into an errors.MultiError and handle it using single error idioms, which is useful for managing subtasks.
package main import ( "sync" "time" "github.com/secureworks/errors" ) type wrapperType struct{} func (_ *wrapperType) ReturnError() error { return errors.NewWithFrame("err from wrapper type") } func runSomeTask(n int) error { var wrapper *wrapperType time.Sleep(time.Duration(100*n) * time.Millisecond) if n%2 == 0 { return errors.Errorf( "while running some task (%d): %w", n, wrapper.ReturnError()) } return nil } // By using errors.Errorf and errors.WithFrame we can add useful // debugging information and error context to go routines, which can be // hard to track down. // // We can also coalesce errors into an errors.MultiError and handle it // using single error idioms, which is useful for managing subtasks. func main() { var wg sync.WaitGroup errCh := make(chan error) for i := 0; i < 4; i++ { wg.Add(1) i := i go func() { defer wg.Done() err := errors.WithFrame(runSomeTask(i)) if err != nil { errCh <- err } }() } go func() { wg.Wait() close(errCh) }() var merr error for err := range errCh { errors.AppendInto(&merr, err) } if merr != nil { pprintf("%+v", merr) } }
Output: multiple errors: * error 1 of 2: while running some task (0): err from wrapper type github.com/secureworks/errors_test.(*wrapperType).ReturnError /home/testuser/pkgs/errors/example_debug_tasks_test.go:0 github.com/secureworks/errors_test.runSomeTask /home/testuser/pkgs/errors/example_debug_tasks_test.go:0 github.com/secureworks/errors_test.Example_debugTasks.func1 /home/testuser/pkgs/errors/example_debug_tasks_test.go:0 * error 2 of 2: while running some task (2): err from wrapper type github.com/secureworks/errors_test.(*wrapperType).ReturnError /home/testuser/pkgs/errors/example_debug_tasks_test.go:0 github.com/secureworks/errors_test.runSomeTask /home/testuser/pkgs/errors/example_debug_tasks_test.go:0 github.com/secureworks/errors_test.Example_debugTasks.func1 /home/testuser/pkgs/errors/example_debug_tasks_test.go:0
Example (StreamErrors) ¶
Here we can see that it's straighforward to send errors over some pipe by serializing into bytes.
package main import ( "bufio" "bytes" "fmt" "io" "github.com/secureworks/errors" ) var splitTokensOn = []byte("\n\n") // Crate a scanner that tokenizes on two newlines. func tokenizer(data []byte, atEOF bool) (advance int, token []byte, err error) { if atEOF && len(data) == 0 { return 0, nil, nil } if i := bytes.Index(data, splitTokensOn); i >= 0 { // We have a full newline-terminated line. return i + 2, data[0:i], nil } if atEOF { return len(data), data, nil } return 0, nil, nil } // Here we can see that it's straighforward to send errors over some // pipe by serializing into bytes. func main() { r, w := io.Pipe() errFrames := errors.NewWithFrame("err w frames") errFrames = errors.Errorf("inner context: %w", errFrames) errStack := errors.NewWithStackTrace("err w stack") go func() { var errs = []error{ errors.Errorf("outer context: %w", errFrames), errors.New("basic err"), errStack, } for _, err := range errs { fmt.Fprintf(w, "%+v%s", err, splitTokensOn) } w.Close() }() scanner := bufio.NewScanner(r) scanner.Split(tokenizer) for scanner.Scan() { err, _ := errors.ErrorFromBytes(scanner.Bytes()) pprintf("\nREAD IN ERROR: %+v\n", err) } }
Output: READ IN ERROR: outer context: inner context: err w frames github.com/secureworks/errors_test.Example_streamErrors /home/testuser/pkgs/errors/example_stream_errors_test.go:0 github.com/secureworks/errors_test.Example_streamErrors /home/testuser/pkgs/errors/example_stream_errors_test.go:0 github.com/secureworks/errors_test.Example_streamErrors.func1 /home/testuser/pkgs/errors/example_stream_errors_test.go:0 READ IN ERROR: basic err READ IN ERROR: err w stack github.com/secureworks/errors_test.Example_streamErrors /home/testuser/pkgs/errors/example_stream_errors_test.go:0 testing.runExample /go/src/testing/run_example.go:0 testing.runExamples /go/src/testing/example.go:0 testing.(*M).Run /go/src/testing/testing.go:0 main.main _testmain.go:0 runtime.main /go/src/runtime/proc.go:0
Index ¶
- func Append(errs ...error) error
- func AppendInto(receivingErr *error, appendingErr error) bool
- func AppendResult(receivingErr *error, resulterFn ErrorResulter)
- func As(err error, target interface{}) bool
- func ErrorFromBytes(byt []byte) (err error, ok bool)
- func Errorf(format string, values ...interface{}) error
- func ErrorsFrom(err error) []error
- func Is(err, target error) bool
- func Join(errs ...error) error
- func Mask(err error) error
- func New(text string) error
- func NewWithFrame(msg string) error
- func NewWithFrameAt(msg string, skipCallers int) error
- func NewWithFrames(msg string, ff Frames) error
- func NewWithStackTrace(msg string) error
- func Opaque(err error) error
- func PCFromFrame(v interface{}) uintptr
- func Unwrap(err error) error
- func WithFrame(err error) error
- func WithFrameAt(err error, skipCallers int) error
- func WithFrames(err error, ff Frames) error
- func WithMessage(err error, msg string) error
- func WithStackTrace(err error) error
- type ErrorResulter
- type Frame
- type Frames
- type MultiError
Examples ¶
- Package (DebugTasks)
- Package (StreamErrors)
- Append
- AppendInto
- AppendResult
- Caller
- Errorf
- Errorf (AppendingDebuggingContext)
- ErrorsFrom
- ErrorsFrom (Nil)
- ErrorsFrom (SingleError)
- Frame (Printf)
- Frame (ProgramCounter)
- FrameFromPC
- Frames
- Frames (JsonMarshal)
- Frames (Printf)
- FramesFrom (AppendedFrames)
- FramesFrom (StackTrace)
- FramesFromBytes
- FramesFromJSON
- Mask
- MultiError (As)
- MultiError (Is)
- MultiError (Printf)
- MultiError.ErrorOrNil
- MultiError.Unwrap
- New
- NewFrame
- NewMultiError
- NewMultiError (FlattensMultiErrors)
- NewMultiError (IsAnError)
- NewWithFrame
- NewWithFrameAt
- NewWithFrames
- NewWithStackTrace
- Opaque
- PCFromFrame (RuntimeFrame)
- PCFromFrame (RuntimePC)
- PCFromFrame (RuntimeProgramCounter)
- WithFrame
- WithFrameAt
- WithFrames
- WithMessage
- WithStackTrace
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Append ¶
Append is a version of NewMultiError optimized for the common case of merging a small group of errors and expecting the outcome to be an error or nil, akin to the standard library's errors.Join (and it is, in fact, used for this library's implementation of Join).
The following pattern may also be used to record failure of deferred operations without losing information about the original error.
func doSomething(..) (err error) { f := acquireResource() defer func() { err = errors.Append(err, f.Close()) }()
Example ¶
package main import ( "fmt" "github.com/secureworks/errors" ) func main() { err := errors.Append( nil, nil, ) fmt.Printf("\n%v", err) err = errors.Append(err, nil) fmt.Printf("\n%v", err) err = errors.Append( err, errors.New("err1"), ) fmt.Printf("\n%v", err) err = errors.Append(err, nil) fmt.Printf("\n%v", err) err = errors.Append( err, errors.New("err2"), ) fmt.Printf("\n%v", err) err = errors.Append(err, nil) fmt.Printf("\n%v", err) err = errors.Append( err, errors.New("err3"), ) fmt.Printf("\n%v", err) }
Output: <nil> <nil> err1 err1 [err1; err2] [err1; err2] [err1; err2; err3]
func AppendInto ¶
AppendInto appends an error into the destination of an error pointer and returns whether the error being appended was non-nil.
var err error errors.AppendInto(&err, r.Close()) errors.AppendInto(&err, w.Close())
The above is equivalent to,
err := errors.Append(r.Close(), w.Close())
As AppendInto reports whether the provided error was non-nil, it may be used to build an errors error in a loop more ergonomically. For example:
var err error for line := range lines { var item Item if errors.AppendInto(&err, parse(line, &item)) { continue } items = append(items, item) } if err != nil { log.Fatal(err) }
Example ¶
package main import ( "fmt" "github.com/secureworks/errors" ) func main() { var aerr error errs := []error{ nil, errors.New("err1"), errors.New("err2"), errors.New("err3"), } for _, err := range errs { errors.AppendInto(&aerr, err) fmt.Printf("\n%s", aerr) } }
Output: %!s(<nil>) err1 [err1; err2] [err1; err2; err3]
func AppendResult ¶
func AppendResult(receivingErr *error, resulterFn ErrorResulter)
AppendResult appends the result of calling the given ErrorResulter into the provided error pointer. Use it with named returns to safely defer invocation of fallible operations until a function returns, and capture the resulting errors.
func doSomething(...) (err error) { // ... f, err := openFile(..) if err != nil { return err } // errors will call f.Close() when this function returns, and if the // operation fails it will append its error into the returned error. defer errors.AppendInvoke(&err, f.Close) scanner := bufio.NewScanner(f) // Similarly, this scheduled scanner.Err to be called and inspected // when the function returns and append its error into the returned // error. defer errors.AppendResult(&err, scanner.Err) // ... }
Without defer, AppendResult behaves exactly like AppendInto.
err := // ... errors.AppendResult(&err, errorableFn) // ...is roughly equivalent to... err := // ... errors.AppendInto(&err, errorableFn())
The advantage of the indirection introduced by ErrorResulter is to make it easy to defer the invocation of a function. Without this indirection, the invoked function will be evaluated at the time of the defer block rather than when the function returns.
// BAD: This is likely not what the caller intended. This will evaluate // foo() right away and append its result into the error when the // function returns. defer errors.AppendInto(&err, errorableFn()) // GOOD: This will defer invocation of foo until the function returns. defer errors.AppendResult(&err, errorableFn)
Example ¶
package main import ( "fmt" "github.com/secureworks/errors" ) type testErrCloser struct{} func (_ *testErrCloser) Close() error { return errors.New("and a closer error to boot!") } func main() { errFn := func() (err error) { closer := &testErrCloser{} defer errors.AppendResult(&err, closer.Close) err = errors.New("some error we got") if err != nil { return } return } noErrFn := func() (err error) { closer := &testCloser{} defer errors.AppendResult(&err, closer.Close) return } fmt.Println() err := errFn() if err != nil { fmt.Println(err) } err = noErrFn() if err != nil { fmt.Println(err) } else { fmt.Println("noErrFn returned nil") } } type testCloser struct{ err error } func (t testCloser) Close() error { return t.err }
Output: [some error we got; and a closer error to boot!] noErrFn returned nil
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.
func ErrorFromBytes ¶
ErrorFromBytes parses a stack trace or stack dump provided as bytes into an error. The format of the text is expected to match the output of printing with a formatter using the `%+v` verb. When an error is successfully parsed the second result is true; otherwise it is false. If you receive an error and the second result is false, well congrats you got an error.
Currently, this only supports single errors with or without a stack trace or appended frames.
TODO(PH): ensure ErrorFromBytes works with: multierror.
func Errorf ¶
Errorf is a shorthand for:
fmt.Errorf("some msg: %w", errors.WithFrame(err))
It is made available to support the best practice of adding a call stack frame to the error context alongside a message when building a chain. When possible, prefer using the full syntax instead of this shorthand for clarity.
Similar to fmt.Errorf, this function supports multiple `%w` verbs to generate a multierror: each wrapped error will have a frame attached to it.
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { err := errors.New("err message") err = errors.Errorf("outer context: %w", err) pprintf("%+v", err) }
Output: outer context: err message github.com/secureworks/errors_test.ExampleErrorf /home/testuser/pkgs/errors/examples_test.go:0
Example (AppendingDebuggingContext) ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { err := errors.New("err message") err = errors.Errorf("context: %w", err) err = errors.Errorf("outermost context: %w", err) pprintf("%+v", err) }
Output: outermost context: context: err message github.com/secureworks/errors_test.ExampleErrorf_appendingDebuggingContext /home/testuser/pkgs/errors/examples_test.go:0 github.com/secureworks/errors_test.ExampleErrorf_appendingDebuggingContext /home/testuser/pkgs/errors/examples_test.go:0
func ErrorsFrom ¶
ErrorsFrom returns a list of errors that the supplied error is composed of. If the error is a multierror then Unwrap is called on it. If the given error is nil, a nil slice is returned. If the error is not composed of other errors, the returned slice contains just the error that was passed in.
ErrorsFrom is useful when it is unknown if a given error is a multierror or not.
Callers of this function are free to modify the returned slice, but modifications to the errors themselves may race.
Example ¶
package main import ( "fmt" "github.com/secureworks/errors" ) func main() { err := errors.NewMultiError( errors.New("err1"), errors.New("err2"), errors.New("err3"), ).ErrorOrNil() fmt.Println(err) // Print the multierror for comparison. errs := errors.ErrorsFrom(err) for _, err := range errs { fmt.Println(err) } }
Output: [err1; err2; err3] err1 err2 err3
Example (Nil) ¶
package main import ( "fmt" "github.com/secureworks/errors" ) func main() { errs := errors.ErrorsFrom(nil) for _, err := range errs { // errs has 0 length. fmt.Println(err) } }
Output:
Example (SingleError) ¶
package main import ( "fmt" "github.com/secureworks/errors" ) func main() { err := errors.New("err") err = errors.Errorf("inner context: %w", err) err = errors.Errorf("outer context: %w", err) errs := errors.ErrorsFrom(err) for _, err := range errs { // errs contains the given error. fmt.Println(err) } }
Output: outer context: inner context: err
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.
func Join ¶ added in v0.2.0
Join returns an error that wraps the given errors. Any nil error values are discarded. Join returns nil if every value in errs is nil. The error formats as the concatenation of the strings obtained by calling the Error method of each element of errs.
A non-nil error returned by Join implements the Unwrap() []error method.
Since you are using the github.com/secureworks/errors package, be aware that this returns a MultiError instead of the default standard library's implementation.
func Mask ¶
Mask returns an error with the same message context as err, but that does not match err and can't be unwrapped. As and Is will return false for all meaningful values.
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { err := errors.New("new err message") err = errors.Errorf("context: %w", err) err = errors.Errorf("outermost context: %w", err) err = errors.Mask(errors.WithMessage(err, "err")) // Should show frames. pprintf("%+v", err) }
Output: err
func New ¶
New returns an error that formats as the given text. Each call to New returns a distinct error value even if the text is identical.
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { err := errors.New("err message") pprintf("%+v", err) }
Output: err message
func NewWithFrame ¶
NewWithFrame returns a new error annotated with a call stack frame.
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { err := errors.NewWithFrame("err message") pprintf("%+v", err) }
Output: err message github.com/secureworks/errors_test.ExampleNewWithFrame /home/testuser/pkgs/errors/examples_test.go:0
func NewWithFrameAt ¶
NewWithFrameAt returns a new error annotated with a call stack frame. The second param allows you to tune how many callers to skip (in case this is called in a helper you want to ignore, for example).
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { err := errors.NewWithFrameAt("err message", 1) pprintf("%+v", err) }
Output: err message testing.runExample /go/src/testing/run_example.go:0
func NewWithFrames ¶
NewWithFrames returns a new error annotated with a list of frames.
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { frames := errors.CallStackAtMost(0, 2) err := errors.NewWithFrames("err message", frames) pprintf("%+v", err) }
Output: err message github.com/secureworks/errors_test.ExampleNewWithFrames /home/testuser/pkgs/errors/examples_test.go:0 testing.runExample /go/src/testing/run_example.go:0
func NewWithStackTrace ¶
NewWithStackTrace returns a new error annotated with a stack trace.
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { err := errors.NewWithStackTrace("err message") pprintf("%+v", err) }
Output: err message github.com/secureworks/errors_test.ExampleNewWithStackTrace /home/testuser/pkgs/errors/examples_test.go:0 testing.runExample /go/src/testing/run_example.go:0 testing.runExamples /go/src/testing/example.go:0 testing.(*M).Run /go/src/testing/testing.go:0 main.main _testmain.go:0 runtime.main /go/src/runtime/proc.go:0
func Opaque ¶
Opaque returns an error with the same message context as err, but that does not match err. As and Is will return false for all meaningful values.
If err is a chain with Frames, then those are retained as wrappers around the opaque error, so that the error does not lose any information. Otherwise, err cannot be unwrapped.
You can think of Opaque as squashing the history of an error.
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } type unknownErrorType struct { error error SecretValue string } func (e *unknownErrorType) Error() string { return e.error.Error() } func (e *unknownErrorType) Unwrap() error { return e.error } func main() { err := errors.New("err message") err = errors.Errorf("context: %w", err) err = &unknownErrorType{error: err, SecretValue: "secret data we don't want to leak"} err = errors.Errorf("outermost context: %w", err) err = errors.Opaque(err) // Opaque squashes the error chain, removing any outside types that may // have snuck in, while retaining all the errors package data we know // about. var unkErr *unknownErrorType if errors.As(err, &unkErr) { fmt.Println("leaked data:", unkErr.SecretValue) } pprintf("%+v", err) }
Output: outermost context: context: err message github.com/secureworks/errors_test.ExampleOpaque /home/testuser/pkgs/errors/examples_test.go:0 github.com/secureworks/errors_test.ExampleOpaque /home/testuser/pkgs/errors/examples_test.go:0
func PCFromFrame ¶
func PCFromFrame(v interface{}) uintptr
PCFromFrame extracts the frame location program counter (pc) from either this package's Frame implementation (using an unexported interface), a raw uintptr (for identity), or runtime.Frame. Does not distinguish between an empty or nil frame, an unsupported frame implementation, or some other error: all return 0.
Example (RuntimeFrame) ¶
package main import ( "fmt" "runtime" "github.com/secureworks/errors" ) func main() { var pcs [1]uintptr runtime.Callers(0, pcs[:]) frames := runtime.CallersFrames(pcs[:]) frame, _ := frames.Next() pc := errors.PCFromFrame(frame) fmt.Printf(" %t", pc == pcs[0]-1) }
Output: true
Example (RuntimePC) ¶
package main import ( "fmt" "runtime" "github.com/secureworks/errors" ) func main() { framePC, _, _, _ := runtime.Caller(0) pc := errors.PCFromFrame(framePC) fmt.Printf("%t", pc == framePC) }
Output: true
Example (RuntimeProgramCounter) ¶
package main import ( "fmt" "github.com/secureworks/errors" ) func main() { type programCounter interface { PC() uintptr } fr := errors.Caller() pcer, _ := fr.(programCounter) pc := errors.PCFromFrame(fr) fmt.Printf(" %t", pc == pcer.PC()) }
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.
func WithFrame ¶
WithFrame adds a call stack frame to the error by wrapping it.
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { err := errors.New("err message") err = errors.WithFrame(err) pprintf("%+v", err) }
Output: err message github.com/secureworks/errors_test.ExampleWithFrame /home/testuser/pkgs/errors/examples_test.go:0
func WithFrameAt ¶
WithFrameAt adds a call stack frame to the error by wrapping it. The second param allows you to tune how many callers to skip (in case this is called in a helper you want to ignore, for example).
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { err := errors.New("err message") err = errors.WithFrameAt(err, 1) pprintf("%+v", err) }
Output: err message testing.runExample /go/src/testing/run_example.go:0
func WithFrames ¶
WithFrames adds a list of frames to the error by wrapping it.
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { err := errors.New("err message") frames := errors.CallStackAtMost(0, 2) err = errors.WithFrames(err, frames) pprintf("%+v", err) }
Output: err message github.com/secureworks/errors_test.ExampleWithFrames /home/testuser/pkgs/errors/examples_test.go:0 testing.runExample /go/src/testing/run_example.go:0
func WithMessage ¶
WithMessage overwrites the message for the error by wrapping it. The error chain is maintained so that As, Is, and FramesFrom all continue to work.
Example ¶
package main import ( "fmt" "github.com/secureworks/errors" ) func main() { err := errors.New("new err message") err = errors.Errorf("context: %w", err) err = errors.Errorf("outermost context: %w", err) err = errors.WithMessage(err, "new err message") fmt.Print(err) }
Output: new err message
func WithStackTrace ¶
WithStackTrace adds a stack trace to the error by wrapping it.
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { err := errors.New("err message") err = errors.WithStackTrace(err) pprintf("%+v", err) }
Output: err message github.com/secureworks/errors_test.ExampleWithStackTrace /home/testuser/pkgs/errors/examples_test.go:0 testing.runExample /go/src/testing/run_example.go:0 testing.runExamples /go/src/testing/example.go:0 testing.(*M).Run /go/src/testing/testing.go:0 main.main _testmain.go:0 runtime.main /go/src/runtime/proc.go:0
Types ¶
type ErrorResulter ¶
type ErrorResulter func() error
ErrorResulter is a function that may fail with an error. Use it with AppendResult to append the result of calling the function into an error. This allows you to conveniently defer capture of failing operations.
type Frame ¶
type Frame interface { // Location returns the frame's caller's characteristics for help with // identifying and debugging the codebase. // // Location results are generated uniquely per Frame implementation. // When using this package's implementation, note that the results are // evaluated and expanded lazily when the frame was generated from the // local call stack: Location is not safe for concurrent access. Location() (function string, file string, line int) }
Frame defines an interface for accessing and displaying stack frame information for debugging, optimizing or inspection. Usually you will find Frame in a Frames slice, acting as a stack trace or stack dump.
Frames are meant to be seen, so we have implemented the following default formatting verbs on it:
"%s" – the base name of the file (or `unknown`) and the line number (if known) "%q" – the same as `%s` but wrapped in `"` delimiters "%d" – the line number "%n" – the basic function name, ie without a full package qualifier "%v" – the full path of the file (or `unknown`) and the line number (if known) "%+v" – a standard line in a stack trace: a full function name on one line, and a full file name and line number on a second line "%#v" – a Golang representation with the type (`errors.Frame`)
Marshaling a frame as text uses the `%+v` format. Marshaling as JSON returns an object with location data:
{"function":"test.pkg.in/example.init","file":"/src/example.go","line":10}
A Frame is immutable, so no setters are provided, but you can copy one trivially with:
function, file, line := oldFrame.Location() newFrame := errors.NewFrame(function, file, line)
Example (Printf) ¶
The underlying type generated here implements fmt.Formatter.
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { fr := errors.Caller() fmt.Println() pprintf("%%s: %s\n", fr) pprintf("%%q: %q\n", fr) pprintf("%%n: %n\n", fr) pprintf("%%d: %d\n", fr) pprintf("%%v: %v\n", fr) pprintf("%%#v: %#v\n", fr) pprintf("%%+v: %+v\n", fr) }
Output: %s: examples_test.go:0 %q: "examples_test.go:0" %n: ExampleFrame_printf %d: 77 %v: /home/testuser/pkgs/errors/examples_test.go:0 %#v: errors.Frame("/home/testuser/pkgs/errors/examples_test.go:0") %+v: github.com/secureworks/errors_test.ExampleFrame_printf /home/testuser/pkgs/errors/examples_test.go:0
Example (ProgramCounter) ¶
The underlying type generated here implements the unexported interface programCounter.
package main import ( "fmt" "github.com/secureworks/errors" ) func main() { type programCounter interface { PC() uintptr } localFr, ok := errors.Caller().(programCounter) if !ok { panic(errors.New("well this is a fine predicament")) } synthFr, ok := errors.NewFrame("fn.name", "file.go", 10).(programCounter) if !ok { panic(errors.New("well this is a fine predicament")) } // Who knows what the actual pointer value is: >0 means it was generated // from a local call stack, while 0 means it was created synthetically. fmt.Printf("%t %t", localFr.PC() > 0, synthFr.PC() > 0) }
Output: true false
func Caller ¶
func Caller() Frame
Caller returns a Frame that describes the proximate frame on the caller's stack.
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprint allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprint(v ...interface{}) { entries := strings.Split(fmt.Sprint(v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { fr := errors.Caller() pprint(fr) }
Output: /home/testuser/pkgs/errors/examples_test.go:0
func CallerAt ¶
CallerAt returns a Frame that describes a frame on the caller's stack. The argument skipCaller is the number of frames to skip over.
func FrameFromPC ¶
FrameFromPC creates a Frame from a program counter.
Example ¶
package main import ( "fmt" "regexp" "runtime" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { pc, _, _, _ := runtime.Caller(0) fr := errors.FrameFromPC(pc) pprintf("%+v", fr) }
Output: github.com/secureworks/errors_test.ExampleFrameFromPC /home/testuser/pkgs/errors/examples_test.go:0
func NewFrame ¶
NewFrame creates a "synthetic" Frame that describes the given location characteristics. This can be used to deserialize stack traces or stack dumps, or write clear tests that work with these.
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { fr := errors.NewFrame("fn.name", "file.go", 10) pprintf("%+v", fr) }
Output: fn.name file.go:0
type Frames ¶
type Frames []Frame
Frames is a slice of Frame data. This can represent a stack trace or some subset of a stack trace.
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprint allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprint(v ...interface{}) { entries := strings.Split(fmt.Sprint(v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { stack := errors.CallStack() pprint(stack) }
Output: [/home/testuser/pkgs/errors/examples_test.go:0 /go/src/testing/run_example.go:0 /go/src/testing/example.go:0 /go/src/testing/testing.go:0 _testmain.go:0 /go/src/runtime/proc.go:0]
Example (JsonMarshal) ¶
The underlying types generated by errors implement json.Marshaler. This may not hold for slices of other types that implement errors.Frame.
package main import ( "encoding/json" "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { stack := errors.CallStack()[0:1] // Remove stdlib frames. byt, err := json.MarshalIndent(stack, "", " ") if err != nil { panic(errors.New("well this is a fine predicament")) } pprintf("\n%s", string(byt)) }
Output: [ { "function": "github.com/secureworks/errors_test.ExampleFrames_jsonMarshal", "file": "/home/testuser/pkgs/errors/examples_test.go", "line": 180 } ]
Example (Printf) ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { stack := errors.CallStack() fmt.Println() pprintf("%+v", stack) }
Output: github.com/secureworks/errors_test.ExampleFrames_printf /home/testuser/pkgs/errors/examples_test.go:0 testing.runExample /go/src/testing/run_example.go:0 testing.runExamples /go/src/testing/example.go:0 testing.(*M).Run /go/src/testing/testing.go:0 main.main _testmain.go:0 runtime.main /go/src/runtime/proc.go:0
func CallStack ¶
func CallStack() Frames
CallStack returns all the Frames that describe the caller's stack.
func CallStackAt ¶
CallStackAt returns all the Frames that describe the caller's stack. The argument skipCaller is the number of frames to skip over.
func CallStackAtMost ¶
CallStackAtMost returns a subset of Frames that describe the caller's stack. The argument skipCaller is the number of frames to skip over, and the argument maxFrames is the maximum number of frames to return (if the entire stack is less than maxFrames, the entireStack is returned). maxFrames of zero or fewer is ignored:
CallStackAtMost(0, 0) // ... returns the entire stack for the caller
func FramesFrom ¶
FramesFrom extracts all the Frames annotated across an error chain in order (if any). To do this it traverses the chain while aggregating frames.
If this method finds any frames on an error that were added as a stack trace (ie, the error was wrapped by WithStackTrace) then the stack trace deepest in the chain is returned alone, ignoring all other stack traces and frames. This lets us we retain the most information possible without returning a confusing frame set. Therefore, try not to mix the WithFrame and WithStackTrace patterns in a single error chain.
FramesFrom will not traverse a multierror, since there is no sensible way to structure the returned frames.
Example (AppendedFrames) ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { err := errors.New("err message") err = errors.Errorf("context: %w", err) err = errors.Errorf("outermost context: %w", err) frames := errors.FramesFrom(err) pprintf("\n%+v", frames) }
Output: github.com/secureworks/errors_test.ExampleFramesFrom_appendedFrames /home/testuser/pkgs/errors/examples_test.go:0 github.com/secureworks/errors_test.ExampleFramesFrom_appendedFrames /home/testuser/pkgs/errors/examples_test.go:0
Example (StackTrace) ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { err := errors.NewWithStackTrace("err message") err = errors.Errorf("context: %w", err) err = errors.Errorf("outermost context: %w", err) frames := errors.FramesFrom(err) pprintf("\n%+v", frames) }
Output: github.com/secureworks/errors_test.ExampleFramesFrom_stackTrace /home/testuser/pkgs/errors/examples_test.go:0 testing.runExample /go/src/testing/run_example.go:0 testing.runExamples /go/src/testing/example.go:0 testing.(*M).Run /go/src/testing/testing.go:0 main.main _testmain.go:0 runtime.main /go/src/runtime/proc.go:0
func FramesFromBytes ¶
FramesFromBytes parses a stack trace or stack dump provided as bytes into a stack of Frames. The format of the text is expected to match the output of printing with a formatter using the `%+v` verb.
Example ¶
package main import ( "fmt" "github.com/secureworks/errors" ) func main() { stackDump := []byte(`err message github.com/secureworks/errors_test.FnName /home/testuser/pkgs/errors/examples_test.go:0 github.com/secureworks/errors_test.FnWrapper /home/testuser/pkgs/errors/examples_test.go:0 runtime.main /go/src/runtime/proc.go:0 `) stack, _ := errors.FramesFromBytes(stackDump) fmt.Printf("\n%+v", stack) }
Output: github.com/secureworks/errors_test.FnName /home/testuser/pkgs/errors/examples_test.go:0 github.com/secureworks/errors_test.FnWrapper /home/testuser/pkgs/errors/examples_test.go:0 runtime.main /go/src/runtime/proc.go:0
func FramesFromJSON ¶
FramesFromJSON parses a stack trace or stack dump provided as JSON-encoded bytes into a stack of Frames. json. Unmarshal does not work because it is meant to marshal into pre-allocated items, where Frames are defined only as interfaces.
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { rawJSON := []byte(`[ { "function": "github.com/secureworks/errors_test.FnName", "file": "/home/testuser/pkgs/errors/examples_test.go", "line": 200 }, { "function": "github.com/secureworks/errors_test.FnWrapper", "file": "/home/testuser/pkgs/errors/examples_test.go", "line": 190 }, { "function": "runtime.main", "file": "/go/src/runtime/proc.go", "line": 255 } ]`) stack, _ := errors.FramesFromJSON(rawJSON) pprintf("\n%+v", stack) }
Output: github.com/secureworks/errors_test.FnName /home/testuser/pkgs/errors/examples_test.go:0 github.com/secureworks/errors_test.FnWrapper /home/testuser/pkgs/errors/examples_test.go:0 runtime.main /go/src/runtime/proc.go:0
func (Frames) MarshalJSON ¶
type MultiError ¶
type MultiError struct {
// contains filtered or unexported fields
}
MultiError is a list of errors. For compatibility, this type also implements the standard library error interface we refer to in this documentation as "multierror:"
type interface { Unwrap() []error }
MultiError includes helpers for managing groups of errors using Go patterns. They are not synchronized for concurrent use, but can be used in concurrent code if the user manages synchronization.
The MultiError pattern is for top-level collection of error groups only, and are flattened when appended: no errors contained in its list are a multierror. (This is the major difference with the standard library implementation, which stores errors as a tree if they are progressively built). This flattening is not recursive, however: if a multierror is wrapped inside another error, it is not flattened, since this could cause us to lose information or context.
Unlike some error collection / multiple-error packages, we rely on an exported MultiError type to make it obvious how it should be handled in the codebase. While it can be treated as an error when necessary, we must be vigilant about nil-checking with MultiError:
if merr := errors.NewMultiError(nil); merr != nil { // This will always be true! } // Instead, check the length of the errors: if merr := errors.NewMultiError(nil); len(merr.Unwrap()) > 0 { // This works ... } // Or use ErrorsOrNil to get a clean error interface: if merr := errors.NewMultiError(nil); merr.ErrorOrNil() != nil { // This works ... }
For simple error-joining, use Append or AppendInto, which only speak in the error interface.
Example (As) ¶
package main import ( "fmt" "github.com/secureworks/errors" ) type unknownErrorType struct { error error SecretValue string } func (e *unknownErrorType) Error() string { return e.error.Error() } func (e *unknownErrorType) Unwrap() error { return e.error } func main() { err1 := errors.Errorf("context: %w", &unknownErrorType{error: errors.New("err"), SecretValue: "secret A"}) fmt.Println() var unkErr *unknownErrorType if errors.As(err1, &unkErr) { fmt.Printf("basic unwrap found: %s\n", unkErr.SecretValue) } else { fmt.Println("basic unwrap not found") } // MultiError implements As by iterating over each error in order, // unwrapping the contained values. err2 := errors.Errorf("outer context: %w", errors.NewMultiError( errors.New("err"), err1, // Last in order, so not reached. &unknownErrorType{error: errors.New("err"), SecretValue: "secret B"}, )) if errors.As(err2, &unkErr) { fmt.Printf("multi unwrap found: %s\n", unkErr.SecretValue) } else { fmt.Println("multi unwrap not found") } // To get all, you must unwrap to MultiError and then unwrap contained values. var merr *errors.MultiError if errors.As(err2, &merr) { for i, err := range merr.Unwrap() { if errors.As(err, &unkErr) { fmt.Printf("unmerged %d unwrap found: %s\n", i, unkErr.SecretValue) } else { fmt.Printf("unmerged %d unwrap not found\n", i) } } } }
Output: basic unwrap found: secret A multi unwrap found: secret A unmerged 0 unwrap not found unmerged 1 unwrap found: secret A unmerged 2 unwrap found: secret B
Example (Is) ¶
package main import ( "fmt" "github.com/secureworks/errors" ) func main() { errSentinel := errors.New("sentinel err") errA := errors.Errorf("ctx A: %w", errSentinel) errB := errors.Errorf("ctx B: %w", errors.New("err")) errC := errors.Errorf("ctx C: %w", errSentinel) fmt.Println() // MultiError implements Is by iterating over each error in order, // unwrapping the contained values. err := errors.Errorf("outer context: %w", errors.NewMultiError( errA, errB, errC, )) if errors.Is(err, errSentinel) { fmt.Printf("multi err sentinel found: %s\n", err) } else { fmt.Println("multi err sentinel not found") } // To check all, you must unwrap to MultiError and then check contained // values. var merr *errors.MultiError if errors.As(err, &merr) { for i, err := range merr.Unwrap() { if errors.Is(err, errSentinel) { fmt.Printf("unmerged %d sentinel found: %s\n", i, err) } else { fmt.Printf("unmerged %d sentinel not found\n", i) } } } }
Output: multi err sentinel found: outer context: [ctx A: sentinel err; ctx B: err; ctx C: sentinel err] unmerged 0 sentinel found: ctx A: sentinel err unmerged 1 sentinel not found unmerged 2 sentinel found: ctx C: sentinel err
Example (Printf) ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprintf allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprintf(format string, v ...interface{}) { entries := strings.Split(fmt.Sprintf(format, v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { merrEmpty := errors.NewMultiError() merrFull := errors.NewMultiError( errors.New("err1"), errors.NewWithFrame("err2"), errors.NewWithStackTrace("err3"), ) merrWrapped := errors.Errorf("context: %w", merrFull) fmt.Println() pprintf("1. %+v\n", merrEmpty) pprintf("2. %+v\n", merrFull) pprintf("3. %+v\n", merrWrapped) }
Output: 1. empty errors: [] 2. multiple errors: * error 1 of 3: err1 * error 2 of 3: err2 github.com/secureworks/errors_test.ExampleMultiError_printf /home/testuser/pkgs/errors/examples_test.go:0 * error 3 of 3: err3 github.com/secureworks/errors_test.ExampleMultiError_printf /home/testuser/pkgs/errors/examples_test.go:0 testing.runExample /go/src/testing/run_example.go:0 testing.runExamples /go/src/testing/example.go:0 testing.(*M).Run /go/src/testing/testing.go:0 main.main _testmain.go:0 runtime.main /go/src/runtime/proc.go:0 3. context: [err1; err2; err3] github.com/secureworks/errors_test.ExampleMultiError_printf /home/testuser/pkgs/errors/examples_test.go:0
func NewMultiError ¶
func NewMultiError(errs ...error) (merr *MultiError)
NewMultiError returns a MultiError from a group of errors. Nil error values are not included, so the size of the MultiError may be less than the number of errors passed to the function.
If any of the given errors is a multierror, it is flattened into the new MultiError, allowing "append-like" behavior.
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprint allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprint(v ...interface{}) { entries := strings.Split(fmt.Sprint(v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { merr := errors.NewMultiError( errors.New("err1"), errors.New("err2"), errors.New("err3"), ) pprint(merr) }
Output: [err1; err2; err3]
Example (FlattensMultiErrors) ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprint allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprint(v ...interface{}) { entries := strings.Split(fmt.Sprint(v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { merrInner := errors.NewMultiError( errors.New("err1"), errors.New("err2"), ) merr := errors.NewMultiError( merrInner, errors.New("err3"), ) pprint(merr) }
Output: [err1; err2; err3]
Example (IsAnError) ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprint allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprint(v ...interface{}) { entries := strings.Split(fmt.Sprint(v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { err := (error)(errors.NewMultiError( errors.New("err1"), errors.New("err2"), errors.New("err3"), )) pprint(err) }
Output: [err1; err2; err3]
func (*MultiError) Error ¶
func (merr *MultiError) Error() string
func (*MultiError) ErrorOrNil ¶
func (merr *MultiError) ErrorOrNil() error
ErrorOrNil is used to get a clean error interface for reflection, nil checking and other comparisons. If the MultiError is empty it returns nil, and if there is a single error then it is unnested. Otherwise, it returns a MultiError retyped for the error interface.
Retrieving the MultiError is simple, since NewMultiError flattens MultiErrors passed to it:
err := errors.NewMultiError(e1, e2, e3).ErrorOrNil() newMErr := errors.NewMultiError(err) newMErr.Errors() // => []error{e1, e2, e3}
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprint allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprint(v ...interface{}) { entries := strings.Split(fmt.Sprint(v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { pprint("\n", errors.NewMultiError().ErrorOrNil()) pprint("\n", errors.NewMultiError(nil).ErrorOrNil()) pprint("\n", errors.NewMultiError(errors.New("err")).ErrorOrNil()) pprint("\n", errors.NewMultiError(errors.New("err"), errors.New("err")).ErrorOrNil()) }
Output: <nil> <nil> err [err; err]
func (*MultiError) Errors
deprecated
func (merr *MultiError) Errors() []error
Errors is the version v0.1 interface for multierrors. This pre-dated the release of Go 1.20, so Unwrap() []error was not a clear standard yet. It now is.
Deprecated: use Unwrap instead.
func (*MultiError) Unwrap ¶
func (merr *MultiError) Unwrap() []error
Unwrap returns the underlying value of the MultiError: a slice of errors. It returns a nil slice if the error is nil or has no errors.
This interface may be used to handle multierrors in code that may not want to expect a MultiError type directly:
if merr, ok := err.(interface{ Unwrap() [] error }); ok { // ... }
Do not modify the returned errors and expect the MultiError to remain stable.
Example ¶
package main import ( "fmt" "regexp" "strings" "github.com/secureworks/errors" ) var sharedPath = "/home/testuser/pkgs/errors/" var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`) var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`) var matchLineNumbers = regexp.MustCompile(`:[0-9]+`) // pprint allows these tests to pass in any environment by grepping // filepaths in the output, and to ease matching by removing line // numbers from the call stacks. func pprint(v ...interface{}) { entries := strings.Split(fmt.Sprint(v...), " ") for i := range entries { entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/") entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath) entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0") } fmt.Print(strings.Join(entries, " ")) } func main() { merr := errors.NewMultiError( errors.New("err1"), errors.New("err2"), errors.New("err3"), ) for _, err := range merr.Unwrap() { pprint("\n", err) } }
Output: err1 err2 err3
Source Files ¶
Directories ¶
Path | Synopsis |
---|---|
internal
|
|
constraints
Package constraints should only be used as a blank import.
|
Package constraints should only be used as a blank import. |
Package syncerr provides synchronization utilities for working with errors generated by goroutines.
|
Package syncerr provides synchronization utilities for working with errors generated by goroutines. |