i want to know why fmt.Println()
in Go/golang does print all the errors of some error-chain, when all wrapped errors in that error-chain where created with fmt.Errorf()
. But when one of these errors is a custom error (that also wraps), fmt.Println()
stops unwrapping and printing of error chain, at the custom error position.
Here is some simplified sample code:
type CustomError struct {
Msg string
Err error
}
func (e *CustomError) Error() string {
return e.Msg
}
func (e *CustomError) Unwrap() error {
return e.Err
}
func main() {
level1Err := errors.New("[Error in L1]: Boom")
level2Err := fmt.Errorf("[Error in L2]: Wrap L1Err %w", level1Err)
level3Err := fmt.Errorf("[Error in L3]: Wrap L2Err %w", level2Err)
//level3Err := &CustomError{"[Error in L3]: Wrap L2Err", level2Err}
level4Err := fmt.Errorf("[Error in L4]: Wrap L3Err %w", level3Err)
fmt.Println(level4Err)
}
// Console output, when uncomment line28 and comment line29:
// [Error in L4]: Wrap L3Err [Error in L3]: Wrap L2Err [Error in L2]: Wrap L1Err [Error in L1]: Boom
// Console output, when uncomment line29 and comment line28:
// [Error in L4]: Wrap L3Err [Error in L3]: Wrap L2Err
Using Go 1.18
You can also found the sample code at https://github.com/MBODM/golang-error-chains-problem
Any help is rather appreciated
CodePudding user response:
If you look at an existing example from go source code:
// SyscallError records an error from a specific system call.
type SyscallError struct {
Syscall string
Err error
}
func (e *SyscallError) Error() string { return e.Syscall ": " e.Err.Error() }
func (e *SyscallError) Unwrap() error { return e.Err }
So your CustomError#Error()
method should be:
func (e *CustomError) Error() string {
if e.Err == nil {
return e.Msg
}
return e.Msg ": " e.Err.Error()
}
See this playground example:
[Error in L4]: Wrap L3Err [ErrorC in L3]: Wrap L2Err: [Error in L2]: Wrap L1Err [Error in L1]: Boom
CodePudding user response:
To help other ppls like me (i had a hard time with this, as a Go newbie), i answer my own question here:
It seems fmt.Println(topLevelErr)
NOT unwraps all the errors and prints all that errors (by calling .Error()
of every unwrapped error) with some ": "
as delimiter between the errors, as i thought initially. This is NOT what happens.
Instead all the go-lib errors do this by themselfes, by implementing the error
interface in this way: Their Error()
method (error
implementation) tests if they have some wrapped error inside of themselfes and if so, print it "chained". This means they print their own error msg ": "
the wrapped error´s msg.
You can see this, if you have a look at the Go source code, as VonC mentioned below.
I personally would call this a rather stupid design. And totally unnecessary too, since all errors are wrapped anyway. Therefore all errors can be unwrapped manually, via errors.Unwrap()
method from go-lib. When unwrapped we (or fmt.Println()
) could print the error msg of every error, together with some delimiting ": "
stuff.
Also i advice you to do the same thing as i did in sample code below: At least let fmt.Errorf()
create that wrapped error msg, instead of putting some ": "
string into it, by yourself. So it is granted, when the wrapped error behaviour changes, yours do also.
This shows, what´s all about:
type CustomError struct {
Msg string
Err error
}
func (e *CustomError) Error() string {
if e.Err != nil {
wrappedError := fmt.Errorf("%s %w", e.Msg, e.Err)
wrappedErrorMsg := wrappedError.Error()
return wrappedErrorMsg
}
return e.Msg
}
func (e *CustomError) Unwrap() error {
return e.Err
}
func printAllWrappedErrors(topLevelError error) {
fmt.Println(topLevelError)
}
func printCustomErrorOnly(topLevelError error) {
var e *CustomError
if errors.As(topLevelError, &e) {
fmt.Println(e.Msg) // <-- This is the difference
}
}
func printCustomErrorIncludingAllWrappedErrors(topLevelError error) {
var e *CustomError
if errors.As(topLevelError, &e) {
fmt.Println(e) // <-- This is the difference
}
}
// Console output, of the 3 funcs above:
// Print1 --> [Error in L4]: Wrap L3Err [Error in L3]: Wrap L2Err [Error in L2]: Wrap L1Err [Error in L1]: Boom
// Print2 --> [Error in L3]: Wrap L2Err
// Print3 --> [Error in L3]: Wrap L2Err [Error in L2]: Wrap L1Err [Error in L1]: Boom
For a better understanding just play around with the sample. You can find it here on GitHub.
And big THX to VonC for the prompt help and the Kickoff! Was very helpful! :)
Have fun.