Create a custom error type that embeds error and includes a stack trace field, then use runtime.Callers to capture the stack when instantiating it.
package main
import (
"errors"
"fmt"
"runtime"
"strings"
)
type StackError struct {
msg string
stack []uintptr
}
func (e *StackError) Error() string {
return e.msg
}
func (e *StackError) Stack() string {
var sb strings.Builder
sb.WriteString("Stack trace:\n")
for _, pc := range e.stack {
frames := runtime.CallersFrames([]uintptr{pc})
for {
frame, more := frames.Next()
sb.Printf("%s:%d\n", frame.File, frame.Line)
if !more {
break
}
}
}
return sb.String()
}
func NewStackError(msg string) error {
const depth = 32
var pcs [depth]uintptr
n := runtime.Callers(2, pcs[:])
return &StackError{msg: msg, stack: pcs[:n]}
}
func main() {
err := NewStackError("something went wrong")
if err != nil {
fmt.Println(err)
if se, ok := err.(*StackError); ok {
fmt.Println(se.Stack())
}
}
}