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  }