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 }