github.com/sunvim/utils@v0.1.0/errors/stack.go (about) 1 package errors 2 3 import ( 4 "fmt" 5 "io" 6 "path" 7 "runtime" 8 "strings" 9 ) 10 11 // Frame represents a program counter inside a stack frame. 12 type Frame uintptr 13 14 // pc returns the program counter for this frame; 15 // multiple frames may have the same PC value. 16 func (f Frame) pc() uintptr { return uintptr(f) - 1 } 17 18 // file returns the full path to the file that contains the 19 // function for this Frame's pc. 20 func (f Frame) file() string { 21 fn := runtime.FuncForPC(f.pc()) 22 if fn == nil { 23 return "unknown" 24 } 25 file, _ := fn.FileLine(f.pc()) 26 return file 27 } 28 29 // line returns the line number of source code of the 30 // function for this Frame's pc. 31 func (f Frame) line() int { 32 fn := runtime.FuncForPC(f.pc()) 33 if fn == nil { 34 return 0 35 } 36 _, line := fn.FileLine(f.pc()) 37 return line 38 } 39 40 // Format formats the frame according to the fmt.Formatter interface. 41 // 42 // %s source file 43 // %d source line 44 // %n function name 45 // %v equivalent to %s:%d 46 // 47 // Format accepts flags that alter the printing of some verbs, as follows: 48 // 49 // %+s function name and path of source file relative to the compile time 50 // GOPATH separated by \n\t (<funcname>\n\t<path>) 51 // %+v equivalent to %+s:%d 52 func (f Frame) Format(s fmt.State, verb rune) { 53 switch verb { 54 case 's': 55 switch { 56 case s.Flag('+'): 57 pc := f.pc() 58 fn := runtime.FuncForPC(pc) 59 if fn == nil { 60 io.WriteString(s, "unknown") 61 } else { 62 file, _ := fn.FileLine(pc) 63 fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) 64 } 65 default: 66 io.WriteString(s, path.Base(f.file())) 67 } 68 case 'd': 69 fmt.Fprintf(s, "%d", f.line()) 70 case 'n': 71 name := runtime.FuncForPC(f.pc()).Name() 72 io.WriteString(s, funcname(name)) 73 case 'v': 74 f.Format(s, 's') 75 io.WriteString(s, ":") 76 f.Format(s, 'd') 77 } 78 } 79 80 // StackTrace is stack of Frames from innermost (newest) to outermost (oldest). 81 type StackTrace []Frame 82 83 // Format formats the stack of Frames according to the fmt.Formatter interface. 84 // 85 // %s lists source files for each Frame in the stack 86 // %v lists the source file and line number for each Frame in the stack 87 // 88 // Format accepts flags that alter the printing of some verbs, as follows: 89 // 90 // %+v Prints filename, function, and line number for each Frame in the stack. 91 func (st StackTrace) Format(s fmt.State, verb rune) { 92 switch verb { 93 case 'v': 94 switch { 95 case s.Flag('+'): 96 for _, f := range st { 97 fmt.Fprintf(s, "\n%+v", f) 98 } 99 case s.Flag('#'): 100 fmt.Fprintf(s, "%#v", []Frame(st)) 101 default: 102 fmt.Fprintf(s, "%v", []Frame(st)) 103 } 104 case 's': 105 fmt.Fprintf(s, "%s", []Frame(st)) 106 } 107 } 108 109 // stack represents a stack of program counters. 110 type stack []uintptr 111 112 func (s *stack) Format(st fmt.State, verb rune) { 113 switch verb { 114 case 'v': 115 switch { 116 case st.Flag('+'): 117 for _, pc := range *s { 118 f := Frame(pc) 119 fmt.Fprintf(st, "\n%+v", f) 120 } 121 } 122 } 123 } 124 125 func (s *stack) StackTrace() StackTrace { 126 f := make([]Frame, len(*s)) 127 for i := 0; i < len(f); i++ { 128 f[i] = Frame((*s)[i]) 129 } 130 return f 131 } 132 133 func callers() *stack { 134 const depth = 32 135 var pcs [depth]uintptr 136 n := runtime.Callers(3, pcs[:]) 137 var st stack = pcs[0:n] 138 return &st 139 } 140 141 // funcname removes the path prefix component of a function's name reported by func.Name(). 142 func funcname(name string) string { 143 i := strings.LastIndex(name, "/") 144 name = name[i+1:] 145 i = strings.Index(name, ".") 146 return name[i+1:] 147 }