callers.go (2161B)
1 package errors 2 3 import ( 4 "encoding/json" 5 "runtime" 6 "strconv" 7 "strings" 8 "unsafe" 9 ) 10 11 // Callers is a stacktrace of caller PCs. 12 type Callers []uintptr 13 14 // GetCallers returns a Callers slice of PCs, of at most 'depth'. 15 func GetCallers(skip int, depth int) Callers { 16 rpc := make([]uintptr, depth) 17 n := runtime.Callers(skip+1, rpc) 18 return Callers(rpc[0:n]) 19 } 20 21 // Frames fetches runtime frames for a slice of caller PCs. 22 func (f Callers) Frames() []runtime.Frame { 23 // Allocate expected frames slice 24 frames := make([]runtime.Frame, 0, len(f)) 25 26 // Get frames iterator for PCs 27 iter := runtime.CallersFrames(f) 28 29 for { 30 // Get next frame in iter 31 frame, ok := iter.Next() 32 if !ok { 33 break 34 } 35 36 // Append to frames slice 37 frames = append(frames, frame) 38 } 39 40 return frames 41 } 42 43 // MarshalJSON implements json.Marshaler to provide an easy, simple default. 44 func (f Callers) MarshalJSON() ([]byte, error) { 45 // JSON-able frame type 46 type jsonFrame struct { 47 Func string `json:"func"` 48 File string `json:"file"` 49 Line int `json:"line"` 50 } 51 52 // Convert to frames 53 frames := f.Frames() 54 55 // Allocate expected size jsonFrame slice 56 jsonFrames := make([]jsonFrame, 0, len(f)) 57 58 for i := 0; i < len(frames); i++ { 59 frame := frames[i] 60 61 // Convert each to jsonFrame object 62 jsonFrames = append(jsonFrames, jsonFrame{ 63 Func: funcname(frame.Function), 64 File: frame.File, 65 Line: frame.Line, 66 }) 67 } 68 69 // marshal converted frames 70 return json.Marshal(frames) 71 } 72 73 // String will return a simple string representation of receiving Callers slice. 74 func (f Callers) String() string { 75 // Guess-timate to reduce allocs 76 buf := make([]byte, 0, 64*len(f)) 77 78 // Convert to frames 79 frames := f.Frames() 80 81 for i := 0; i < len(frames); i++ { 82 frame := frames[i] 83 84 // Append formatted caller info 85 fn := funcname(frame.Function) 86 buf = append(buf, fn+"()\n\t"+frame.File+":"...) 87 buf = strconv.AppendInt(buf, int64(frame.Line), 10) 88 buf = append(buf, '\n') 89 } 90 91 return *(*string)(unsafe.Pointer(&buf)) 92 } 93 94 // funcname splits a function name with pkg from its path prefix. 95 func funcname(name string) string { 96 i := strings.LastIndex(name, "/") 97 return name[i+1:] 98 }