An Error Wrapping Strategy for Go
Tags: go, errors
Published: 2018-11-20 21:07:39

---
title: An Error Wrapping Strategy for Go
published: true
description: go has some pretty bare bones error handling. It can be awesome
tags: go, golang, errors
cover_image: https://thepracticaldev.s3.amazonaws.com/i/5fkv4io0qefdnsirnfs8.jpeg
---

Does this look output familiar?

```
error running program: error parsing yaml: error opening file: error no such file "somefile.yaml"
```

The above is a pseudo-stack trace that you've likely encountered. It's better than just getting `error no such file "somefile.yaml"` as your error since this at least gives a bit of context as to where the error came from. But it's not great. It's hard to trace the code and not very pretty to read. The code that generated this error might look like this:


```go
func main() {
	if err := run(); err != nil {
		fmt.Printf("error running program: %v\n", err)
	}
}

func run() error {
	if err := parseYAML("somefile.yaml"); err != nil {
		return fmt.Errorf("error parsing yaml: %v\n", err)
	}
	return nil
}

func parseYAML(file string) error {
	_, err := os.Open(file)
	if err != nil {
		return fmt.Errorf("error opening file: %v", err)
	}
	return nil
}
```
[Link to the go playground with the code above](https://play.golang.org/p/YAclBp1ebMQ).

This method is both prone to programmer error and it can still be a pain to trace the code without line numbers and file information. The improvement that can be made to this code is to record the stack trace!

The code above should look more like this after this improvement:

```go
func main() {
	if err := run(); err != nil {
		fmt.Printf("encountered an error: %v\n", err)
		if stackerr, ok := err.(StackTrace); ok {
			fmt.Println(string(stackerr.StackTrace()))
		}
	}
}

func run() error {
	return parseYAML("somefile.yaml")
}

// A local error type that holds a stack trace
type Error struct {
	Err   error
	Stack []byte
}
func (e *Error) Error() string {
	return e.Err.Error()
}
func (e *Error) StackTrace() []byte {
	return e.Stack
}

type StackTrace interface {
	StackTrace() []byte
}

func parseYAML(file string) error {
	_, err := os.Open(file)
	if err != nil {
		return &Error{
			// keep the original error that was found
			Err: err,
			// Modify it with the stack trace
			Stack: debug.Stack(),
		}
	}
	return nil
}
```
[Link to the go playground with the code above](https://play.golang.org/p/Rg66y0QBCc8)

Now the output specifies exactly what error occurred and where it occurred.

The rules for this refactoring are pretty straightforward:

1. Wrap errors at the edge of your program.
2. Print the stack at the top level. 

### Rule 1

Technically, this rule should be "wrap any error that doesn't already have a stack trace" but since there is no guarantee that libraries will add a stack trace to the errors they return it is easier to assume no library will add a stack trace and to always add one. The standard library is no exception to this rule.

### Rule 2

Printing or logging the stack is very important, especially when the system/environment that generated the stack is unaccessible. In the open source world it's very common to get an issue where the submitter copy and pastes the output of the command they ran and the error they encountered. If your error doesn't have a stack trace, good luck trying to get someone to asynchronously rerun your program or a custom binary or with a special flag under the same circumstance. Some people do and that's great, but it would be fantastic if this information were available on every issue.

If you're looking for a library that does this for you, look no further than https://github.com/pkg/errors.