Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error handling using /pkg/errors together with golang 1.13 formatting verb %w

I would like to annotate errors with stack traces, therefore I am using /pkg/errors package.

Go 1.13 added the %w - formatting verb to Wrap errors.

The following program not using %w prints a nice stack trace:

https://play.golang.org/p/eAwMrwqjCWX

The following only slightly modified program using %w not:

https://play.golang.org/p/am34kdC0E3o

How am I supposed to use %w together with error wrapping and stack traces?

like image 654
JohnDoe Avatar asked Oct 30 '25 15:10

JohnDoe


1 Answers

The reason is that errors.Errorf initializes new fundamental error type (in pkg/errors). If you will look at its method Format(s fmt.State, verb rune) (which is triggered when you do log.Printf):

func (f *fundamental) Format(s fmt.State, verb rune) {
    switch verb {
    case 'v':
        if s.Flag('+') {
            io.WriteString(s, f.msg)
            f.stack.Format(s, verb)
            return
        }
        fallthrough
    case 's':
        io.WriteString(s, f.msg)
    case 'q':
        fmt.Fprintf(s, "%q", f.msg)
    }
}

So you see that it just prints io.WriteString(s, f.msg). Also, Errorf is:

func Errorf(format string, args ...interface{}) error {
    return &fundamental{
        msg:   fmt.Sprintf(format, args...),
        stack: callers(),
    }
}

Because of the line: fmt.Sprintf(format, args...) you see the error of go vet (you can't pass %w to Sprintf), and in the print of the error msg you just see:

Es gab einen Fehler: %!w(*errors.fundamental=&{failing unconditionally 0xc0000a2000})

Whereas when using Wrap method, you get withStack error type, which its Format method is:

func (w *withStack) Format(s fmt.State, verb rune) {
    switch verb {
    case 'v':
        if s.Flag('+') {
            fmt.Fprintf(s, "%+v", w.Cause())
            w.stack.Format(s, verb)
            return
        }
        fallthrough
    case 's':
        io.WriteString(s, w.Error())
    case 'q':
        fmt.Fprintf(s, "%q", w.Error())
    }
}

Thanks to this line fmt.Fprintf(s, "%+v", w.Cause()), you see a nice recursive stack trace.

Basically, you need to use %w like this:

fmt.Errorf("got error: %w", err)

But this won't help in case you want to print the stack.

What you can do maybe is to implement yourself a function for recursively printing the stack trace, which will work on an error chain of either fmt or pkg/errors:

type stackTracer interface {
             StackTrace() errors.StackTrace
}

type unwrapper interface {
    Unwrap() error
}

func printStack(err error) {
    if err == nil {
        return
    }
    
    if ster, ok := err.(stackTracer); ok {
        fmt.Printf("%+v", ster)
    }
    
    
    if wrapped, ok := err.(unwrapper); ok {
        printStack(wrapped.Unwrap())
    }
}

https://play.golang.org/p/OsEPD6guWtO

like image 67
oren Avatar answered Nov 01 '25 13:11

oren



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!