When writing error handling code in Go, you often want stack traces — but there aren’t many libraries that provide them in a nice, practical way.

For example:

  • pkg/errors allows multiple errors.Wrap calls, but stack traces get duplicated. The maintenance has stopped, and newer APIs like errors.Join are not supported.
  • cockroachdb/errors is well-designed, but the amount of information it outputs can be overwhelming and often requires additional processing.

With these challenges in mind, I created my own error handling library: stackprune/errors.

Goals and Design

  • Provide pkg/errors compatible APIs (Wrap, WithStack, etc.)
  • Avoid duplicated stack traces even when wrapping multiple times
  • Fully interoperable with Go’s standard errors package
  • Easy integration with structured logging tools like slog

Error handling has been a long-discussed topic in Go. My goal was to find a “practical middle ground” that works well in real-world applications.

Usage

Here’s a simple example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package main

import (
	"fmt"
	"log/slog"
	"os"

	"github.com/stackprune/errors"
)

func doSomething() error {
	return errors.New("something went wrong")
}

func handler() error {
	err := doSomething()
	if err != nil {
		return errors.Wrap(err, "failed to handle request")
	}

	return nil
}

func main() {
	logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

	if err := handler(); err != nil {
		fmt.Printf("error: %+v\n", err)
		// Output example:
		// error: failed to handle request: something went wrong
		// main.doSomething
		//         /app/main.go:12
		// main.handler
		//         /app/main.go:16
		// main.main
		//         /app/main.go:27

		logger.Error("error:", slog.Any("error", err))
		// Output example (formatted as single-line JSON):
		// {
		//   "time": "2025-06-17T09:43:54.625751251+09:00",
		//   "level": "ERROR",
		//   "msg": "error:",
		//   "error": {
		//     "message": "failed to handle request: something went wrong",
		//     "kind": "*errors.Error",
		//     "stack": [
		//       "main.doSomething at /app/main.go:12",
		//       "main.handler at /app/main.go:16",
		//       "main.main at /app/main.go:27"
		//     ]
		//   }
		// }
	}
}

Internally, once WithStack or New is called, the stack trace is captured. When wrapped afterward, the same stack is preserved.

Quick Comparison

Library Features
Go standard Is/As are useful; stack traces require custom code
pkg/errors Compatible API, but deprecated; stack duplication
stackprune/errors Prevents stack duplication; integrates with slog

Installation

1
go get github.com/stackprune/errors

Roadmap

  • Improve GoDoc documentation
  • Stabilize versioning

Conclusion

Error handling often becomes increasingly important as projects grow, but there are still few libraries that hit the right balance. I initially built stackprune/errors for my own use case, but if others have similar needs, I’d be happy if you give it a try.