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 }