I am using Go and stumbled over cockroachdb/errors for error handling. I understand that errors.Wrap
can be used to add more context and stack traces to errors, like this:
func DoSomething() error {
err := os.WriteFile(...)
if err != nil {
return errors.Wrap(err, "something happened here in DoSomething")
}
}
func CallSomething() error {
err := DoSomething()
if err != nil {
return errors.WithDetail(err, "here is some additional info")
}
}
Assuming, that the usage above is correct, I am not sure how to handle errors from third-party libraries. Consider the following:
func DoSomething() error {
err := some.ThirdPartyLibraryCode(...)
if err != nil {
return errors.Wrap(err, "something happened here in DoSomething")
}
}
If the third-party library also uses cockroachdb/errors
, wrapping their errors with errors.Wrap
might result in nested errors with multiple stack traces, which could produce confusing output:
(1) attached stack trace
-- stack trace:
| graph-editor/nodes.(*Graph).ExecuteImpl
| /Users/daniel/git/graph-main/[email protected]:52
| [...repeated from below...] 👈👈👈👈 ??
Wraps: (2) failed to execute
How should I handle errors from third-party libraries that might already be using cockroachdb/errors
(or not) to avoid duplicated stack traces and keep the error output clear?
According to the documentation, creating an error
with the New()
family offers this options of printing related information:
// - message via `Error()` and formatting using `%v`/`%s`/`%q`.
// - everything when formatting with `%+v`.
// - stack trace and message via `errors.GetSafeDetails()`.
// - stack trace and message in Sentry reports.
You're not telling us how you're printing the infos you find confusing. After playing around a bit with the library, I guess, you're using the +
prefix for the v
verb, e.g. with
fmt.Printf("%+v", myCockroachDBErr)
(Honestly, I don't know why it results in an output like the one you're posting, since according to the fmt documentation this just "adds field names". Go magixx.)
So, with %+v
you're opting for "everything" output, which is probabably rarely what you want (apart from debug sessions). You will see more or less all information the error
struct holds, in a more or less comprehensable way.
If you just want the straight-forward stack being printed, use
fmt.Printf("%v", errors.GetSafeDetails(myCockroachDBErr))
That said, it seems like Wrap()
functions behave rather similar to the New()
functions. That means, Wrap()
creates a new error
, and stores the wrapped one for further reference. While - according to the Wrap() docs - it "retains" the old error's
stack, it's apparently not considered part of the the new error's
stack, which starts right where Wrap()
is invoked.
If you want to ouput the stack of the wrapped error
, you have to unwrap it first:
import (
"fmt"
"github.com/cockroachdb/errors"
)
func main() {
wrappingErr := someFkt1()
fmt.Printf("wrappingErr: %v\n", errors.GetSafeDetails(wrappingErr))
unwrappedErr := errors.UnwrapOnce(errors.UnwrapOnce(wrappingErr)
fmt.Printf("got2: %v\n", errors.GetSafeDetails(unwrappedErr))
return
}
func someFkt1() error {
return someFkt2()
}
func someFkt2() error {
return errors.Wrapf(someFkt3(), "outer error")
}
func someFkt3() error {
return errors.New("inner error")
}
For a better understanding, you can also output "everything" again:
fmt.Printf("everything: %+v\n\n", err)
Which will log smth like:
everything: inner error
(1) attached stack trace
-- stack trace:
| main.someFkt2
| /myPath/main.go:31
| [...repeated from below...] 👈 i.e. /myPath/main.go:28, /myPath/main.go:11, proc.go:267, asm_amd64.s:1650
Wraps: (2) attached stack trace 👈 the one we unwrap in my example
-- stack trace:
| main.someFkt3
| /myPath/main.go:35
| main.someFkt2
| /myPath/main.go:31
| main.someFkt1
| /myPath/main.go:28
| main.cockroachDBErrors
| /myPath/main.go:11
| runtime.main
| /usr/local/go/src/runtime/proc.go:267
| runtime.goexit
| /usr/local/go/src/runtime/asm_amd64.s:1650
Wraps: (3) inner error 👈 looks like even New() counts as wrapping
Error types: (1) *withstack.withStack (2) *withstack.withStack (3) *errutil.leafError 👈 a helpful legend
I figure you could create a loop unwrapping errors
just before you've reached the inmost one, and output that error's
stack trace to see it all from the start. I don't see a more straightforward way, though - errors.UnwrapAll()
returns the the leafError
, which doesn't bear a trace itself.