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:

  1. The errors.Is could always check which the error is;
  2. The errors.Unwrap returns error the first wrapped error directly, which means we can’t get the errors amid the chain;
  3. Calling errors.Unwrap on 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 “[…]”.