github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/internal/utils.go (about) 1 package internal 2 3 import ( 4 "bytes" 5 "fmt" 6 "runtime" 7 "strings" 8 ) 9 10 // IdentifyPanic reports the panic location when a panic happens. 11 func IdentifyPanic(skip int) (location string, frames []runtime.Frame) { 12 var name, file string 13 var line int 14 var pc [16]uintptr 15 16 // Don't use runtime.FuncForPC here, it may give incorrect line number. 17 // 18 // From runtime.Callers' doc: 19 // 20 // To translate these PCs into symbolic information such as function 21 // names and line numbers, use CallersFrames. CallersFrames accounts 22 // for inlined functions and adjusts the return program counters into 23 // call program counters. Iterating over the returned slice of PCs 24 // directly is discouraged, as is using FuncForPC on any of the 25 // returned PCs, since these cannot account for inlining or return 26 // program counter adjustment. 27 28 n := runtime.Callers(3+skip, pc[:]) 29 if n > 0 { 30 callerFrames := runtime.CallersFrames(pc[:n]) 31 foundLoc := false 32 frames = make([]runtime.Frame, 0, n) 33 for { 34 f, more := callerFrames.Next() 35 frames = append(frames, f) 36 if !foundLoc { 37 name, file, line = f.Function, f.File, f.Line 38 foundLoc = !strings.HasPrefix(name, "runtime.") 39 } 40 if !more { 41 break 42 } 43 } 44 } 45 switch { 46 case name != "": 47 location = fmt.Sprintf("%v:%v", name, line) 48 case file != "": 49 location = fmt.Sprintf("%v:%v", file, line) 50 default: 51 location = fmt.Sprintf("pc:%x", pc) 52 } 53 return location, frames 54 } 55 56 func FormatFrames(frames []runtime.Frame) []byte { 57 var buf bytes.Buffer 58 for _, f := range frames { 59 file, line, funcName := f.File, f.Line, f.Function 60 if file == "" { 61 file = "unknown" 62 } 63 if funcName == "" { 64 funcName = "unknown" 65 } 66 if buf.Len() > 0 { 67 buf.WriteByte('\n') 68 } 69 fmt.Fprintf(&buf, "%s:%d (%s)", file, line, funcName) 70 } 71 return buf.Bytes() 72 }