stackerr.go raw
1 //go:build js && wasm
2
3 // Package stackerr adds stack traces to verbose error messages.
4 package stackerr
5
6 import (
7 "fmt"
8 "runtime"
9 "strings"
10 )
11
12 type stackError struct {
13 err error
14 stack *stacktrace
15 }
16
17 // WithStack returns 'err' with a stack trace in its verbose formatter output.
18 // Returns nil if err is nil.
19 func WithStack(err error) error {
20 if err == nil {
21 return nil
22 }
23
24 return &stackError{
25 err: err,
26 stack: collectStacktrace(3),
27 }
28 }
29
30 func (s *stackError) Error() string {
31 return s.err.Error()
32 }
33
34 func (s *stackError) Unwrap() error {
35 return s.err
36 }
37
38 func (s *stackError) Format(f fmt.State, verb rune) {
39 switch verb {
40 case 'v':
41 if f.Flag('+') {
42 fmt.Fprintf(f, "%+v\n%s", s.err, s.stack)
43 return
44 }
45 fmt.Fprint(f, s.Error())
46 case 's':
47 fmt.Fprint(f, s.Error())
48 case 'q':
49 fmt.Fprintf(f, "%q", s.Error())
50 }
51 }
52
53 type stacktrace struct {
54 callers []uintptr
55 }
56
57 func collectStacktrace(skip int) *stacktrace {
58 const (
59 maxFrames = 32
60 )
61 pc := make([]uintptr, maxFrames)
62 n := runtime.Callers(1+skip, pc)
63 return &stacktrace{
64 callers: pc[:n],
65 }
66 }
67
68 func (s *stacktrace) String() string {
69 var sb strings.Builder
70 frames := runtime.CallersFrames(s.callers)
71 for frame, next := frames.Next(); next; frame, next = frames.Next() {
72 funcName := "unknown"
73 if frame.Func != nil {
74 funcName = frame.Func.Name()
75 }
76 sb.WriteString(fmt.Sprintf("%s\n\t%s:%d\n", funcName, frame.File, frame.Line))
77 }
78 return sb.String()
79 }
80