github.com/qioalice/ekago/v3@v3.3.2-0.20221202205325-5c262d586ee4/ekasys/stacktrace.go (about)

     1  // Copyright © 2019-2021. All rights reserved.
     2  // Author: Ilya Stroy.
     3  // Contacts: iyuryevich@pm.me, https://github.com/qioalice
     4  // License: https://opensource.org/licenses/MIT
     5  
     6  package ekasys
     7  
     8  import (
     9  	"io"
    10  	"os"
    11  	"runtime"
    12  	"strings"
    13  )
    14  
    15  // StackTrace is the slice of StackFrames, nothing more.
    16  // Each stack level described separately.
    17  type StackTrace []StackFrame
    18  
    19  // getStackFramePoints returns the stack trace point's slice
    20  // that contains 'count' points and starts from 'skip' depth level.
    21  //
    22  // You can pass any value <= 0 as 'count' to get full stack trace points.
    23  func getStackFramePoints(skip, count int) (framePoints []uintptr) {
    24  
    25  	// allow to get absolutely full stack trace
    26  	// (include 'getStackFramePoints' and 'runtime.Callers' functions)
    27  	if skip < -2 {
    28  		skip = -2
    29  	}
    30  
    31  	// but by default (if 0 has been passed as 'skip', it means to skip
    32  	// these functions ('getStackFramePoints' and 'runtime.Callers'),
    33  	// Thus:
    34  	// skip < -2 => skip = 0 => with these functions
    35  	// skip == 0 => skip = 2 => w/o these functions
    36  	skip += 2
    37  
    38  	// get exactly as many stack trace frames as 'count' is
    39  	// only if 'count' is more than zero
    40  	if count > 0 {
    41  		framePoints = make([]uintptr, count)
    42  		return framePoints[:runtime.Callers(skip, framePoints)]
    43  	}
    44  
    45  	const (
    46  		// how much frame points requested first time
    47  		baseFullStackFramePointsLen int = 32
    48  
    49  		// maximum requested frame points
    50  		maxFullStackFramePointsLen int = 128
    51  	)
    52  
    53  	// runtime.Callers only fills slice we provide which
    54  	// so, if slice is full, reallocate mem and try to request frames again
    55  	framePointsLen := 0
    56  	for count = baseFullStackFramePointsLen; ; count <<= 1 {
    57  
    58  		framePoints = make([]uintptr, count)
    59  		framePointsLen = runtime.Callers(skip, framePoints)
    60  
    61  		if framePointsLen < count || count == maxFullStackFramePointsLen {
    62  			break
    63  		}
    64  	}
    65  
    66  	framePoints = framePoints[:framePointsLen]
    67  	return framePoints[:len(framePoints)-1] // ignore Go internal functions
    68  }
    69  
    70  // GetStackTrace returns the stack trace as StackFrame object's slice,
    71  // that have specified 'depth' and starts from 'skip' depth level.
    72  // Each StackFrame object represents an one stack trace depth-level.
    73  //
    74  // You can pass any value <= 0 as 'depth' to get full stack trace.
    75  func GetStackTrace(skip, depth int) (stacktrace StackTrace) {
    76  
    77  	// see the same code section in 'getStackFramePoints'
    78  	// to more details what happening here with 'skip' arg
    79  	if skip < -3 {
    80  		skip = -3
    81  	}
    82  	skip++
    83  
    84  	// prepare to get runtime.Frame objects:
    85  	// - get stack trace frame points,
    86  	// - create runtime.Frame iterator by frame points from prev step
    87  	framePoints := getStackFramePoints(skip, depth)
    88  	framePointsLen := len(framePoints)
    89  	frameIterator := runtime.CallersFrames(framePoints)
    90  
    91  	// alloc mem for slice that will have as many 'runtime.Frame' objects
    92  	// as many frame points we got
    93  	stacktrace = make([]StackFrame, framePointsLen)
    94  
    95  	i := 0
    96  	for more := true; more && i < framePointsLen; i++ {
    97  		stacktrace[i].Frame, more = frameIterator.Next()
    98  	}
    99  
   100  	// but frameIterator can provide less 'runtime.Frame' objects
   101  	// than we requested -> should fix 'frames' len w/o reallocate
   102  	return stacktrace[:i]
   103  }
   104  
   105  // ExcludeInternal returns stacktrace based on current but with excluded all
   106  // Golang internal functions such as runtime.doInit, runtime.main, etc.
   107  func (s StackTrace) ExcludeInternal() StackTrace {
   108  
   109  	// because some internal golang functions (such as runtime.gopanic)
   110  	// could be embedded to user's function stacktrace's part,
   111  	// we can't just cut and drop last part of stacktrace when we found
   112  	// a function with a "runtime." prefix from the beginning to end.
   113  	// instead, we starting from the end and generating "ignore list" -
   114  	// a special list of stack frames that won't be included to the result set.
   115  
   116  	idx := len(s) - 1
   117  	for continue_ := true; continue_; idx-- {
   118  		continue_ = idx > 0 && (strings.HasPrefix(s[idx].Function, "runtime.") ||
   119  			strings.HasPrefix(s[idx].Function, "testing."))
   120  	}
   121  	return s[:idx+2]
   122  }
   123  
   124  // Write writes generated stacktrace to the w or to the stdout if w == nil.
   125  func (s StackTrace) Write(w io.Writer) (n int, err error) {
   126  
   127  	if w == nil {
   128  		w = os.Stdout
   129  	}
   130  
   131  	for _, frame := range s {
   132  
   133  		nn, err_ := w.Write([]byte(frame.DoFormat()))
   134  		if err_ != nil {
   135  			return n, err_
   136  		}
   137  		n += nn
   138  
   139  		// write \n
   140  		if _, err_ := w.Write([]byte{'\n'}); err_ != nil {
   141  			return n, err_
   142  		}
   143  		n += 1
   144  	}
   145  
   146  	return n, nil
   147  }
   148  
   149  // Print prints generated stacktrace to the w or to the stdout if w == nil.
   150  // Ignores all errors. To write with error tracking use Write method.
   151  func (s StackTrace) Print(w io.Writer) {
   152  	_, _ = s.Write(w)
   153  }