github.com/mailgun/holster/v4@v4.20.0/callstack/callstack.go (about)

     1  package callstack
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"runtime"
     7  	"strconv"
     8  	"strings"
     9  
    10  	pkgerrors "github.com/pkg/errors" //nolint:gomodguard // Legacy code requires deprecated package.
    11  )
    12  
    13  type FrameInfo struct {
    14  	CallStack string
    15  	Func      string
    16  	File      string
    17  	LineNo    int
    18  }
    19  
    20  func GetCallStack(frames pkgerrors.StackTrace) string {
    21  	var trace []string
    22  	for i := len(frames) - 1; i >= 0; i-- {
    23  		trace = append(trace, fmt.Sprintf("%v", frames[i]))
    24  	}
    25  	return strings.Join(trace, " ")
    26  }
    27  
    28  // GetLastFrame returns Caller information on the first frame in the stack trace.
    29  func GetLastFrame(frames pkgerrors.StackTrace) FrameInfo {
    30  	if len(frames) == 0 {
    31  		return FrameInfo{}
    32  	}
    33  	pc := uintptr(frames[0]) - 1
    34  	fn := runtime.FuncForPC(pc)
    35  	if fn == nil {
    36  		return FrameInfo{Func: fmt.Sprintf("unknown func at %v", pc)}
    37  	}
    38  	filePath, lineNo := fn.FileLine(pc)
    39  	return FrameInfo{
    40  		CallStack: GetCallStack(frames),
    41  		Func:      FuncName(fn),
    42  		File:      filePath,
    43  		LineNo:    lineNo,
    44  	}
    45  }
    46  
    47  // FuncName given a runtime function spec returns a short function name in
    48  // format `<package name>.<function name>` or if the function has a receiver
    49  // in format `<package name>.(<receiver>).<function name>`.
    50  func FuncName(fn *runtime.Func) string {
    51  	if fn == nil {
    52  		return ""
    53  	}
    54  	funcPath := fn.Name()
    55  	idx := strings.LastIndex(funcPath, "/")
    56  	if idx == -1 {
    57  		return funcPath
    58  	}
    59  	return funcPath[idx+1:]
    60  }
    61  
    62  type HasStackTrace interface {
    63  	StackTrace() pkgerrors.StackTrace
    64  }
    65  
    66  // CallStack represents a stack of program counters.
    67  type CallStack []uintptr
    68  
    69  func (cs *CallStack) Format(st fmt.State, verb rune) {
    70  	if verb == 'v' && st.Flag('+') {
    71  		for _, pc := range *cs {
    72  			f := pkgerrors.Frame(pc)
    73  			_, _ = fmt.Fprintf(st, "\n%+v", f)
    74  		}
    75  	}
    76  }
    77  
    78  func (cs *CallStack) StackTrace() pkgerrors.StackTrace {
    79  	f := make([]pkgerrors.Frame, len(*cs))
    80  	for i := 0; i < len(f); i++ {
    81  		f[i] = pkgerrors.Frame((*cs)[i])
    82  	}
    83  	return f
    84  }
    85  
    86  // New creates a new CallStack struct from current stack minus 'skip' number of frames.
    87  func New(skip int) *CallStack {
    88  	skip += 2
    89  	const depth = 32
    90  	var pcs [depth]uintptr
    91  	n := runtime.Callers(skip, pcs[:])
    92  	var st CallStack = pcs[0:n]
    93  	return &st
    94  }
    95  
    96  // GoRoutineID returns the current goroutine id.
    97  func GoRoutineID() uint64 {
    98  	b := make([]byte, 64)
    99  	b = b[:runtime.Stack(b, false)]
   100  	b = bytes.TrimPrefix(b, []byte("goroutine "))
   101  	b = b[:bytes.IndexByte(b, ' ')]
   102  	n, _ := strconv.ParseUint(string(b), 10, 64)
   103  	return n
   104  }