1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
package errorx
import (
"fmt"
"io"
"runtime"
"strconv"
"sync"
"sync/atomic"
)
// StackTraceFilePathTransformer is a used defined transformer for file path in stack trace output.
type StackTraceFilePathTransformer func(string) string
// InitializeStackTraceTransformer provides a transformer to be used in formatting of all the errors.
// It is OK to leave it alone, stack trace will retain its exact original information.
// This feature may be beneficial, however, if a shortening of file path will make it more convenient to use.
// One of such examples is to transform a project-related path from absolute to relative and thus more IDE-friendly.
//
// NB: error is returned if a transformer was already registered.
// Transformer is changed nonetheless, the old one is returned along with an error.
// User is at liberty to either ignore it, panic, reinstate the old transformer etc.
func InitializeStackTraceTransformer(transformer StackTraceFilePathTransformer) (StackTraceFilePathTransformer, error) {
stackTraceTransformer.mu.Lock()
defer stackTraceTransformer.mu.Unlock()
old := stackTraceTransformer.transform.Load().(StackTraceFilePathTransformer)
stackTraceTransformer.transform.Store(transformer)
if stackTraceTransformer.initialized {
return old, InitializationFailed.New("stack trace transformer was already set up: %#v", old)
}
stackTraceTransformer.initialized = true
return nil, nil
}
var stackTraceTransformer = struct {
mu *sync.Mutex
transform *atomic.Value
initialized bool
}{
&sync.Mutex{},
&atomic.Value{},
false,
}
func init() {
stackTraceTransformer.transform.Store(transformStackTraceLineNoop)
}
var transformStackTraceLineNoop StackTraceFilePathTransformer = func(line string) string {
return line
}
const (
stackTraceDepth = 128
// tuned so that in all control paths of error creation the first frame is useful
// that is, the frame where New/Wrap/Decorate etc. are called; see TestStackTraceStart
skippedFrames = 6
)
func collectStackTrace() *stackTrace {
var pc [stackTraceDepth]uintptr
depth := runtime.Callers(skippedFrames, pc[:])
return &stackTrace{
pc: pc[:depth],
}
}
type stackTrace struct {
pc []uintptr
causeStackTrace *stackTrace
}
func (st *stackTrace) enhanceWithCause(causeStackTrace *stackTrace) {
st.causeStackTrace = causeStackTrace
}
func (st *stackTrace) Format(s fmt.State, verb rune) {
if st == nil {
return
}
switch verb {
case 'v', 's':
st.formatStackTrace(s)
if st.causeStackTrace != nil {
io.WriteString(s, "\n ---------------------------------- ")
st.causeStackTrace.Format(s, verb)
}
}
}
func (st *stackTrace) formatStackTrace(s fmt.State) {
transformLine := stackTraceTransformer.transform.Load().(StackTraceFilePathTransformer)
pc, cropped := st.deduplicateFramesWithCause()
if len(pc) == 0 {
return
}
frames := frameHelperSingleton.GetFrames(pc)
for _, frame := range frames {
io.WriteString(s, "\n at ")
io.WriteString(s, frame.Function())
io.WriteString(s, "()\n\t")
io.WriteString(s, transformLine(frame.File()))
io.WriteString(s, ":")
io.WriteString(s, strconv.Itoa(frame.Line()))
}
if cropped > 0 {
io.WriteString(s, "\n ...\n (")
io.WriteString(s, strconv.Itoa(cropped))
io.WriteString(s, " duplicated frames)")
}
}
func (st *stackTrace) deduplicateFramesWithCause() ([]uintptr, int) {
if st.causeStackTrace == nil {
return st.pc, 0
}
pc := st.pc
causePC := st.causeStackTrace.pc
for i := 1; i <= len(pc) && i <= len(causePC); i++ {
if pc[len(pc)-i] != causePC[len(causePC)-i] {
return pc[:len(pc)-i], i - 1
}
}
return nil, len(pc)
}
|