github.com/Finschia/finschia-sdk@v0.48.1/types/errors/stacktrace.go (about)

     1  package errors
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"runtime"
     7  	"strings"
     8  
     9  	"github.com/pkg/errors"
    10  )
    11  
    12  func matchesFunc(f errors.Frame, prefixes ...string) bool {
    13  	fn := funcName(f)
    14  	for _, prefix := range prefixes {
    15  		if strings.HasPrefix(fn, prefix) {
    16  			return true
    17  		}
    18  	}
    19  	return false
    20  }
    21  
    22  // funcName returns the name of this function, if known.
    23  func funcName(f errors.Frame) string {
    24  	// this looks a bit like magic, but follows example here:
    25  	// https://github.com/pkg/errors/blob/v0.8.1/stack.go#L43-L50
    26  	pc := uintptr(f) - 1
    27  	fn := runtime.FuncForPC(pc)
    28  	if fn == nil {
    29  		return "unknown"
    30  	}
    31  	return fn.Name()
    32  }
    33  
    34  func fileLine(f errors.Frame) (string, int) {
    35  	// this looks a bit like magic, but follows example here:
    36  	// https://github.com/pkg/errors/blob/v0.8.1/stack.go#L14-L27
    37  	// as this is where we get the Frames
    38  	pc := uintptr(f) - 1
    39  	fn := runtime.FuncForPC(pc)
    40  	if fn == nil {
    41  		return "unknown", 0
    42  	}
    43  	return fn.FileLine(pc)
    44  }
    45  
    46  func trimInternal(st errors.StackTrace) errors.StackTrace {
    47  	// trim our internal parts here
    48  	// manual error creation, or runtime for caught panics
    49  	for matchesFunc(st[0],
    50  		// where we create errors
    51  		"github.com/Finschia/finschia-sdk/types/errors.Wrap",
    52  		"github.com/Finschia/finschia-sdk/types/errors.Wrapf",
    53  		"github.com/Finschia/finschia-sdk/types/errors.WithType",
    54  		// runtime are added on panics
    55  		"runtime.",
    56  		// _test is defined in coverage tests, causing failure
    57  		// "/_test/"
    58  	) {
    59  		st = st[1:]
    60  	}
    61  	// trim out outer wrappers (runtime.goexit and test library if present)
    62  	for l := len(st) - 1; l > 0 && matchesFunc(st[l], "runtime.", "testing."); l-- {
    63  		st = st[:l]
    64  	}
    65  	return st
    66  }
    67  
    68  func writeSimpleFrame(s io.Writer, f errors.Frame) {
    69  	file, line := fileLine(f)
    70  	// cut file at "github.com/"
    71  	// TODO: generalize better for other hosts?
    72  	chunks := strings.SplitN(file, "github.com/", 2)
    73  	if len(chunks) == 2 {
    74  		file = chunks[1]
    75  	}
    76  	fmt.Fprintf(s, " [%s:%d]", file, line)
    77  }
    78  
    79  // Format works like pkg/errors, with additions.
    80  // %s is just the error message
    81  // %+v is the full stack trace
    82  // %v appends a compressed [filename:line] where the error
    83  //
    84  //	was created
    85  //
    86  // Inspired by https://github.com/pkg/errors/blob/v0.8.1/errors.go#L162-L176
    87  func (e *wrappedError) Format(s fmt.State, verb rune) {
    88  	// normal output here....
    89  	if verb != 'v' {
    90  		fmt.Fprint(s, e.Error())
    91  		return
    92  	}
    93  	// work with the stack trace... whole or part
    94  	stack := trimInternal(stackTrace(e))
    95  	if s.Flag('+') {
    96  		fmt.Fprintf(s, "%+v\n", stack)
    97  		fmt.Fprint(s, e.Error())
    98  	} else {
    99  		fmt.Fprint(s, e.Error())
   100  		writeSimpleFrame(s, stack[0])
   101  	}
   102  }
   103  
   104  // stackTrace returns the first found stack trace frame carried by given error
   105  // or any wrapped error. It returns nil if no stack trace is found.
   106  func stackTrace(err error) errors.StackTrace {
   107  	type stackTracer interface {
   108  		StackTrace() errors.StackTrace
   109  	}
   110  
   111  	for {
   112  		if st, ok := err.(stackTracer); ok {
   113  			return st.StackTrace()
   114  		}
   115  
   116  		if c, ok := err.(causer); ok {
   117  			err = c.Cause()
   118  		} else {
   119  			return nil
   120  		}
   121  	}
   122  }