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