- tags: Go
- source: “Wrap and Unwrap Errors in Go (Golang) | Gosamples.Dev.” Accessed October 12, 2022. https://gosamples.dev/wrap-unwrap-errors/.
Overview
Since Go1.13, there is a new feature about error add to go: Wrap & Unwrap errors. Let’s start from a simple example:
package main
import (
  "errors"
  "fmt"
)
var errRollingInTheDeep = errors.New("rolling in the deep")
func doSomeActualJob() error {
  return errRollingInTheDeep
}
func someFrameworkCaller() error {
  // We use fmt.Errorf to wrap error.
  // Notice the "%w" formater here and content surround the "[...]".
  return fmt.Errorf("[someFrameworkCaller] %w", doSomeActualJob())
}
func someEntrance() error {
  return fmt.Errorf("[someUpperCaller] %w", someFrameworkCaller())
}
func main() {
  err := someEntrance()
  if errors.Is(err, errRollingInTheDeep) {
    fmt.Println("This should be")
  } else {
    panic("Oooops!")
  }
  fmt.Printf("The final error: %v\n", err)
  fmt.Printf("Unwrap: %v\n", errors.Unwrap(err))
}
Its output:
This should be
The final error: [someUpperCaller] [someFrameworkCaller] rolling in the deep
Unwrap: [someFrameworkCaller] rolling in the deep
Let’s add more line to see how about to continual unwrap it:
package main
import (
  "errors"
  "fmt"
)
var errRollingInTheDeep = errors.New("rolling in the deep")
func doSomeActualJob() error {
  return errRollingInTheDeep
}
func someFrameworkCaller() error {
  // We use fmt.Errorf to wrap error.
  // Notice the "%w" formater here and content surround the "[...]".
  return fmt.Errorf("[someFrameworkCaller] %w", doSomeActualJob())
}
func someEntrance() error {
  return fmt.Errorf("[someUpperCaller] %w", someFrameworkCaller())
}
func main() {
  err := someEntrance()
  if errors.Is(err, errRollingInTheDeep) {
    fmt.Println("This should be")
  } else {
    panic("Oooops!")
  }
  fmt.Printf("The final error: %v\n", err)
  err = errors.Unwrap(err)
  if errors.Is(err, errRollingInTheDeep) {
    fmt.Println("This should be either")
  } else {
    panic("Oooops!")
  }
  fmt.Printf("Unwrapped error: %v\n", errors.Unwrap(err))
  err = errors.Unwrap(err)
  fmt.Printf("Unwrapped error: %v\n", errors.Unwrap(err))
  err = errors.Unwrap(err)
  fmt.Printf("Still unwrap to see what we have got: %v\n", errors.Unwrap(err))
  err = errors.Unwrap(err)
  fmt.Printf("Still unwrap to see what we have got: %v\n", errors.Unwrap(err))
}
Its output:
This should be
The final error: [someUpperCaller] [someFrameworkCaller] rolling in the deep
This should be either
Unwrapped error: rolling in the deep
Unwrapped error: <nil>
Still unwrap to see what we have got: <nil>
Still unwrap to see what we have got: <nil>
As we can see above, here is the conclusions:
- The errors.Iscould always check which the error is;
- The errors.Unwrapreturns error the first wrapped error directly, which means we can’t get the errors amid the chain;
- Calling errors.Unwrapon a non-wrapped error or nil pointer, the nil pointer will always be returned.
Does it Help for Error Tracing Purpose?
As I already known, some logging frameworks/utilities provide like https://github.com/uber-go/zap provide a mechanic to print the invocation stacktrace of error. I’m wonder if this help.
Let’s see a example:
package main
import (
  "errors"
  "fmt"
  "go.uber.org/zap"
)
var errRollingInTheDeep = errors.New("rolling in the deep")
func doSomeActualJob() error {
  return errRollingInTheDeep
}
func someFrameworkCaller() error {
  // We use fmt.Errorf to wrap error.
  // Notice the "%w" formater here and content surround the "[...]".
  return fmt.Errorf("[someFrameworkCaller] %w", doSomeActualJob())
}
func someEntrance() error {
  return fmt.Errorf("[someUpperCaller] %w", someFrameworkCaller())
}
func main() {
  logger, _ := zap.NewDevelopment()
  defer logger.Sync() // flushes buffer, if any
  err := someEntrance()
  logger.Warn("ERROR", zap.Error(err))
}
Its output:
2022-10-12T10:52:32.792+0800    WARN    demo/main.go:30 ERROR   {"error": "[someUpperCaller] [someFrameworkCaller] rolling in the deep"}
main.main
        /Users/wanghui/codes/notes/demo/main.go:30
runtime.main
        /opt
Ooops, the zap output output stacktrace based where its call, not the error.
We can do it with a Stack field that provided zap.
Conclusion: it does help for error tracing purpose, but only with the provided context that surround by “[…]”.