Increasing Go errors informativeness by adding a stack trace and displaying source fragments

Anton G
4 min readFeb 12, 2019

--

I was coding on PHP and JS for several years when switched to Go. I found it really effective to use and Go became my favorite quite fast.

However, the first negative thing I noticed was a completely uninformative error reporting. One can say, that this is the Go path, errors are not exceptions and so on, but unfortunately these statements did not help me somehow. And every time I get back to something like error: invalid character in the middle of nowhere (in the middle of countless if err != nil { return err }, to be honest). And it was a special fun in case of any production error, that I have no idea how to reproduce and where actually to dig.

I was sure, that this is not only my problem, I set up a solution for this as a package and hope it can help a few more gophers!

Golang error stack trace with source displayed.

You can find my repo on GitHub here https://github.com/ztrue/tracerr

This is what this package provides:

  1. Adds stack trace to error.
  2. Displays source fragments of the stack trace (it’s possible if source code exists in the runtime environment).

Adding a stack trace to error

There are several options to do it:

// Create a new one.
err := tracerr.New("some error")
// The same with Errorf, which works the same way as fmt.Errorf.
err := tracerr.Errorf("some error %d", num)
// Or wrap an existent error.
err = tracerr.Wrap(err)

In case of double wrapping (e.g. on the upper-level function), the wrapper will keep an old trace ignoring the second wrap. It is convenient if one does not know if an error is already wrapped or not.

Your code could look like this:

func decodeFile(path string, data interface{}) error {
b, err := ioutil.ReadFile(path)
if err != nil {
return tracerr.Wrap(err)
}
err = json.Unmarshal(b, data)
// You can safely wrap nil, it will be kept as nil.
return tracerr.Wrap(err)
}

Displaying a stack trace

Once the error finishes its journey through over 9000 if err != nil { return err } and got back to main() (or any other place where you want the error to be handled), probably you will wish to display or log it.

There are two options for this purpose: Print to display the error message and Sprint to return it. Both support these formats:

// Display error message and a stack trace.
tracerr.Print(err)
// Display error message, stack trace and source code fragments (6 lines by default).
tracerr.PrintSource(err)
// The same, but in colour, which is more informative.
tracerr.PrintSourceColor(err)
// You can set up how many lines of source code
// you want to be displayed by passing an additional argument.
tracerr.PrintSource(err, 9)
tracerr.PrintSourceColor(err, 9)
// Or pass 2 additional arguments in order to set up
// how many lines of code to display before and after traced line.
tracerr.PrintSource(err, 5, 2)
tracerr.PrintSourceColor(err, 5, 2)

Answers

I already got some feedback on this package, so let me answer questions that I was already asked.

Q: Seems like it’s only useful for debugging, but you have a debugger in your favorite IDE.
A: Not really, you can also add it to the production app in order to save errors with stack traces and even with source code parts. According to my experience, it makes it much easier to investigate errors later.

Q: This was already implemented in pkg/errors, why just don’t use it instead?
A: Well, I actually used it and it’s a great package, but it did not fit some of my needs, in particular:
1) It does not support displaying source fragments, which is crucial for me.
2) It removes the old stack trace with the new one when wrapping the same error again, which makes the stack trace less informative. And sometimes the only way to figure out if there is a stack trace already is a type assertion which is not handy to do every time.
3) It asks for an additional parameter “message” that is required, which seem like a little overhead for code writing/reading in the most situation I encountered.

Q: Errors in Go are not exceptions, so this is not an appropriate way to handle errors.
A: I agree that errors in Go are not exceptions. For sure, if you have a more convenient method to deal with errors — use it. The described method will fit the needs of those who use errors as exceptions as well and for those errors that are returned back through several if err != nil { return err } places.

Q: Stack trace adds some overhead for performance.
A: That’s true. But it is a valid statement only for hot spots, which is much less than all your code. Just don’t add a stack trace to such a place that is really critical for performance.

So, I hope this package will make your gopher's life easier!

I would be happy to hear any feedback according to https://github.com/ztrue/tracerr

Thanks for reading!

--

--