【Go】Differences in Stack Trace Output Between Error Libraries
Created:
目次
In this article, I compare how stack traces are printed when handling errors in Go using the pkg/errors and cockroachdb/errors libraries.
The README of cockroachdb/errors states:
This library aims to be used as a drop-in replacement to github.com/pkg/errors and Go’s standard errors package.
While cockroachdb/errors is intended as a replacement for pkg/errors, actual usage shows that stack trace output is not identical between the two libraries.
Below are the results of experiments highlighting the differences.
In all cases, stack traces were printed using fmt.Printf("%+v\n", err) for both libraries.
pkg/errors
Wrapping Only at the Error Origin
“Wrapping only at the error origin” refers to the following pattern:
func funcD1() error {
err := funcD2()
return err
}
func funcD2() error {
_, err := os.Open("text.txt")
return errors.Wrap(err, "funcD2")
}Here, funcD2 is the point where the error occurs and is wrapped, while funcD1 simply returns the error without wrapping it again.
In this case, the stack trace looks like this:
open text.txt: no such file or directory
funcD2
main.funcD2
/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:78
main.funcD1
/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:71
main.main
/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:28
runtime.main
/home/newvm/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.21.3.linux-amd64/src/runtime/proc.go:267
runtime.goexit
/home/newvm/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.21.3.linux-amd64/src/runtime/asm_amd64.s:1650Wrapping at Every Level
“Wrapping at every level” refers to the following pattern:
func funcE1() error {
err := funcE2()
return errors.Wrap(err, "funcE1")
}
func funcE2() error {
_, err := os.Open("text.txt")
return errors.Wrap(err, "funcE2")
}In this case, not only funcE2, where the error occurs, but also funcE1 wraps the error.
The resulting stack trace is as follows:
open text.txt: no such file or directory
funcE2
main.funcE2
/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:89
main.funcE1
/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:82
main.main
/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:33
runtime.main
/home/newvm/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.21.3.linux-amd64/src/runtime/proc.go:267
runtime.goexit
/home/newvm/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.21.3.linux-amd64/src/runtime/asm_amd64.s:1650
funcE1
main.funcE1
/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:83
main.main
/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:33
runtime.main
/home/newvm/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.21.3.linux-amd64/src/runtime/proc.go:267
runtime.goexit
/home/newvm/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.21.3.linux-amd64/src/runtime/asm_amd64.s:1650The stack trace is duplicated, making it harder to read.
cockroachdb/errors
Wrapping Only at the Error Origin
As with pkg/errors, “wrapping only at the error origin” looks like this:
func funcB1() error {
err := funcB2()
return err
}
func funcB2() error {
_, err := os.Open("text.txt")
return errors.Wrap(err, "funcB2")
}Here, funcB2 is the origin of the error and performs the wrapping, while funcB1 simply returns the error.
The resulting stack trace is:
funcB2: open text.txt: no such file or directory
(1) attached stack trace
-- stack trace:
| main.funcB2
| /home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:56
| main.funcB1
| /home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:49
| main.main
| /home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:18
| runtime.main
| /home/newvm/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.21.3.linux-amd64/src/runtime/proc.go:267
| runtime.goexit
| /home/newvm/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.21.3.linux-amd64/src/runtime/asm_amd64.s:1650
Wraps: (2) funcB2
Wraps: (3) open text.txt
Wraps: (4) no such file or directory
Error types: (1) *withstack.withStack (2) *errutil.withPrefix (3) *fs.PathError (4) syscall.ErrnoWrapping at Every Level
“Wrapping at every level” refers to the following pattern:
func funcC1() error {
err := funcC2()
return errors.Wrap(err, "funcC1")
}
func funcC2() error {
_, err := os.Open("text.txt")
return errors.Wrap(err, "funcC2")
}In this case, both funcC2 (where the error occurs) and funcC1 wrap the error.
The resulting stack trace is:
funcC1: funcC2: open text.txt: no such file or directory
(1) attached stack trace
-- stack trace:
| main.funcC1
| /home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:61
| [...repeated from below...]
Wraps: (2) funcC1
Wraps: (3) attached stack trace
-- stack trace:
| main.funcC2
| /home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:67
| main.funcC1
| /home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:60
| main.main
| /home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:23
| runtime.main
| /home/newvm/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.21.3.linux-amd64/src/runtime/proc.go:267
| runtime.goexit
| /home/newvm/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.21.3.linux-amd64/src/runtime/asm_amd64.s:1650
Wraps: (4) funcC2
Wraps: (5) open text.txt
Wraps: (6) no such file or directory
Error types: (1) *withstack.withStack (2) *errutil.withPrefix (3) *withstack.withStack (4) *errutil.withPrefix (5) *fs.PathError (6) syscall.ErrnoUnlike pkg/errors, cockroachdb/errors detects duplicated stack frames and shortens the output using markers such as [...repeated from below...].
As a result, even when wrapping at every level, the output is less verbose and easier to read.
Additional Notes
The repository used for these experiments is available here:
