Skip to main content

Golang Errors With Stacktrace

·485 words·3 mins
Sebastian Scheibe
Author
Sebastian Scheibe
Table of Contents

Problem
#

The error handling of Go is not up to snuff compared to other languages like Java for example. Errors are treated equally to other values, meaning, they have no special type. This means, they do not carry information of origin in them. Which makes tracking down the error origin more difficult.

Let me show that to you.

package main

import (
	"errors"
	"fmt"
)

func main() {
	err := NewErrorWithStackTrace()
	if err != nil {
		fmt.Println(err)
	}
}

func NewErrorWithStackTrace() error {
	return errors.New("fetching user failed")
}

Which generates this output

➜  go run .
fetching user failed

Solution
#

Well, the output tells us there has been an error, but it does not enlighten us where it originates.

It might no problem to decipher this in a simple application but in a major application, error analysis just costs time.

To address this issue, I discovered the package github.com/go-errors/errors which carries the stack trace from the error creation!

Making effective use of the package is as simple as switching the import. Further on, the stack trace is available as a method. Let me show you!

package main

import (
	"fmt"
	"github.com/go-errors/errors"
)

func main() {
	err := NewErrorWithStackTrace()
	if err != nil {
	    if errors.Is(err, &errors.Error{}) {
		    fmt.Println(err.(*errors.Error).ErrorStack())
		} else {
            fmt.Println(err)
		}
	}
}

func NewErrorWithStackTrace() error {
	return errors.New("fetching user failed")
}

Important in the code change are the change of import and the type casting to errors.Error, which gives the ErrorStack() method. With that change, the following output is generated.

➜  go run .
*errors.errorString fetching user failed
/Users/sebastianscheibe/Code/golang-errors-test/main.go:16 (0x1090aa7)
        main: return errors.New("fetching user failed")
/Users/sebastianscheibe/Code/golang-errors-test/main.go:9 (0x1090a95)
        main: err := UserGet()
/usr/local/opt/go/libexec/src/runtime/proc.go:250 (0x1031fd2)
        main: fn()
/usr/local/opt/go/libexec/src/runtime/asm_amd64.s:1571 (0x105ae01)
        goexit: BYTE    $0x90   // NOP

Stacktrace for Legacy Code Errors
#

As you can see, the stack trace is only available for errors created with the errors.New() function. To also show the stack trace for errors which cannot be converted to errors.Error, we can use the errors.New(err) function. Unfortunately, that gives us only the stack trace from using the errors.New(err) function, not the stack trace from the error creation. But that might be enough to track down the error.

package main

import (
	"fmt"
	"github.com/go-errors/errors"
)

func main() {
	err := LegacyCodeFunc()
	if err != nil {
		handleErrorLog(err)
	}
}

func handleErrorLog(err error) {
	if (errors.Is(err, &errors.Error{})) {
		fmt.Println(err.(*errors.Error).ErrorStack())
	} else {
		fmt.Println(errors.New(err).ErrorStack())
	}
}

With that newly created handleErrorLog function, we can now handle errors of the legacy code and get a stack trace.

➜  go run .                                                                                                                                                                    1 ↵ ──(Thu,Aug10)─┘
*errors.errorString some legacy error without stacktrace, good luck finding the origin
/Users/sebastianscheibe/Code/golang-errors-test/main.go:19 (0x10073907c)
        handleErrorLog: fmt.Println(errors.New(err).ErrorStack())
/Users/sebastianscheibe/Code/golang-errors-test/main.go:11 (0x100738f8c)
        main: handleErrorLog(err)
/opt/homebrew/opt/go/libexec/src/runtime/internal/atomic/types.go:194 (0x1006d6478)
        (*Uint32).Load: return Load(&u.value)
/opt/homebrew/opt/go/libexec/src/runtime/asm_arm64.s:1172 (0x100701a54)
        goexit: MOVD    R0, R0  // NOP

Conclusion
#

The package https://github.com/go-errors/errors is a great tool to get stack traces for errors. It is easy to use and can be used for legacy code as well.