Go

Go errors don't carry stack traces. How do you get stack information when you need it?

Question 315MediumGo 1.22 to 1.25

The standard library leaves stacks out of errors on purpose: capturing them costs time, and well-written wrap messages usually give enough context. When you do need one:

  • runtime/debug.Stack() / debug.PrintStack(): the current goroutine's stack, typically captured in a recover handler.
  • runtime.Callers + runtime.CallersFrames: record program counters cheaply when the error is created, and turn them into symbols only when printing.
  • GOTRACEBACK=all|system|crash controls how much a crash prints (crash also produces a core dump). debug.SetTraceback sets it from code.
  • Third-party packages (pkg/errors, which is archived, or cockroachdb/errors) and log/slog with the source option.
type stackErr struct {
	err error
	pcs []uintptr
}

func (e *stackErr) Error() string { return e.err.Error() }
func (e *stackErr) Unwrap() error { return e.err }

func WithStack(err error) error {
	pcs := make([]uintptr, 32)
	n := runtime.Callers(2, pcs) // skip Callers and WithStack
	return &stackErr{err, pcs[:n]}
}

func (e *stackErr) Stack() string {
	var b strings.Builder
	frames := runtime.CallersFrames(e.pcs)
	for {
		f, more := frames.Next()
		fmt.Fprintf(&b, "%s\n\t%s:%d\n", f.Function, f.File, f.Line)
		if !more {
			return b.String()
		}
	}
}

Capture a stack once, at the lowest point where the error enters your code, not at every layer.

More on Error Handling & panics

All 37 Error Handling & panics questions