💻 Tech
Wrapping errors in Go helps preserve the original error and attach context each time the error is returned up to the call stack. By the time the error is handled, the handler can refer to the original error. There are different ways to wrap errors, but here I listed:
errors.Wrapf
usingerrors
package%w
formatting verb used withfmt.Errorf
function of thefmt
package
With errors.Wrapf
, you can view more details from the error with %+v
, which reveals the stack trace. Without it, it uses the default value returne of the implemented Error() function of the interface. In the example, the default value is customized.
package main
import (
"fmt"
"github.com/pkg/errors"
)
type customError struct {
code int
}
func (c *customError) Error() string {
return fmt.Sprintf("error occurred, code: %v", c.code)
}
func main() {
if err := someFunc1(); err != nil {
fmt.Printf("Stack trace: \n %+v \n", err)
fmt.Printf("No trace: \n %v", err)
}
}
func someFunc1() error {
if err := someFunc2(); err != nil {
return errors.Wrapf(err, "error calling someFunc2")
}
return nil
}
func someFunc2() error {
if err := someFunc3(); err != nil {
return errors.Wrapf(err, "error calling someFunc3")
}
return nil
}
func someFunc3() error {
return &customError{1}
}
Stack trace:
error occurred, code: 1
error calling someFunc3
main.someFunc2
/tmp/sandbox372597777/prog.go:37
main.someFunc1
/tmp/sandbox372597777/prog.go:28
main.main
/tmp/sandbox372597777/prog.go:20
runtime.main
/usr/local/go-faketime/src/runtime/proc.go:272
runtime.goexit
/usr/local/go-faketime/src/runtime/asm_amd64.s:1700
error calling someFunc2
main.someFunc1
/tmp/sandbox372597777/prog.go:29
main.main
/tmp/sandbox372597777/prog.go:20
runtime.main
/usr/local/go-faketime/src/runtime/proc.go:272
runtime.goexit
/usr/local/go-faketime/src/runtime/asm_amd64.s:1700
No trace:
error calling someFunc2: error calling someFunc3: error occurred, code: 1
Program exited.
errors
package is an external package, and you have the option to just use the standard library way with formatting verb %w
with fmt.Errorf
when wrapping the error. It does the same with Wrapf
except that %w
doesn’t track stack trace at the point the error occurred. You can see at the output that %+v
and %v
have the same output.
package main
import (
"fmt"
)
type customError struct {
code int
}
func (c *customError) Error() string {
return fmt.Sprintf("error occurred, code: %v", c.code)
}
func main() {
if err := someFunc1(); err != nil {
fmt.Printf("Stack trace: \n %+v \n", err)
fmt.Printf("No trace: \n %v", err)
}
}
func someFunc1() error {
if err := someFunc2(); err != nil {
return fmt.Errorf("error calling someFunc2: %w", err)
}
return nil
}
func someFunc2() error {
if err := someFunc3(); err != nil {
return fmt.Errorf("error calling someFunc3: %w", err)
}
return nil
}
func someFunc3() error {
return &customError{1}
}
Stack trace:
error calling someFunc2: error calling someFunc3: error occurred, code: 1
No trace:
error calling someFunc2: error calling someFunc3: error occurred, code: 1
Program exited.