Home > OS >  fmt.Println() stops printing chain at wrapped custom error (golang)
fmt.Println() stops printing chain at wrapped custom error (golang)

Time:06-06

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.

  •  Tags:  
  • go
  • Related