How to get the stack trace pointing to actual error reason

Take a look at https://github.com/ztrue/tracerr

I created this package in order to have both stack trace and source fragments to be able to debug faster and log errors with much more details.

Here is a code example:

package main

import (
    "io/ioutil"
    "github.com/ztrue/tracerr"
)

func main() {
    if err := read(); err != nil {
        tracerr.PrintSourceColor(err)
    }
}

func read() error {
    return readNonExistent()
}

func readNonExistent() error {
    _, err := ioutil.ReadFile("/tmp/non_existent_file")
    // Add stack trace to existing error, no matter if it's nil.
    return tracerr.Wrap(err)
}

And here is the output: golang error stack trace


I think that there is an easier way to achieve this. You can try wrapping errors using the golang "default" third party library error package:

You need to define the interface to be implemented by your error :

type stackTracer interface {
    StackTrace() errors.StackTrace
}

Then use it when wrapping/processing an error :

err, ok := errors.(stackTracer) // ok is false if errors doesn't implement stackTracer

stack := err.StackTrace()
fmt.Println(stack) // here you'll have your stack trace

As others have pointed out tracing errors in go isn't trivial. There are projects like juju/errgo, that allow you to wrap errors and then trace these errors back. For that to work tough, you must use them consistently throughout your project and that won't help you with errors in 3rd party libraries or with errors that get handled and never get returned.

Because this is such a common issue and I really got annoyed with this, I wrote a small debug utility that will add debug code to go files that logs every returned error (value that implements error) and the function in which it was returned to STDOUT (if you need more advanced logging just hack the logger in the project, it's really simple).

Installation

go get github.com/gellweiler/errgotrace

Usage

To debug all files in the current directory:

$ find . -name '*.go' -print0 | xargs -0 errgotrace -w

To remove the added debug code from the go files:

$ find . -name '*.go' -print0 | xargs -0 errgotrace -w -r

Then just simply compile & run your code or your test cases.

Sample Output

[...]
2017/12/13 00:54:39 [ERRGOTRACE] parser.*Parser.objectKey: EOF token found
2017/12/13 00:54:39 [ERRGOTRACE] parser.*Parser.objectItem: EOF token found
2017/12/13 00:54:39 [ERRGOTRACE] parser.*Parser.objectKey: EOF token found
2017/12/13 00:54:39 [ERRGOTRACE] parser.*Parser.objectItem: EOF token found
2017/12/13 00:54:39 [ERRGOTRACE] parser.*Parser.objectKey: At 3:4: nested object expected: LBRACE got: ASSIGN
2017/12/13 00:54:39 [ERRGOTRACE] parser.*Parser.objectItem: At 3:4: nested object expected: LBRACE got: ASSIGN
2017/12/13 00:54:39 [ERRGOTRACE] parser.*Parser.objectList: At 3:4: nested object expected: LBRACE got: ASSIGN
2017/12/13 00:54:39 [ERRGOTRACE] parser.*Parser.Parse: At 2:31: literal not terminated
2017/12/13 00:54:39 [ERRGOTRACE] parser.Parse: At 2:31: literal not terminated
2017/12/13 00:54:39 [ERRGOTRACE] hcl.parse: At 2:31: literal not terminated
2017/12/13 00:54:39 [ERRGOTRACE] hcl.ParseBytes: At 2:31: literal not terminated
2017/12/13 00:54:39 [ERRGOTRACE] formula.parse: parsing failed
[...]

As you can see from this output, it's really easy to tell in which function the error originally occurred. Once you know that, you can use a debugger to get more context.


Shortly: this is not possible. Since errors are values, they are not treated in any special way. Due to this, when function (normally) returns, stack is no more available (ie. another function call may overwrite memory used by returning-error function' stack).

There is a tool called trace which was introduced with go1.5, but for now, there is no comprehensive tutorial available neither any of those I found says that this kind of feature will be included.

Tags:

Go