stackerr.go 1.4 KB

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