-
Notifications
You must be signed in to change notification settings - Fork 30
/
stacktrace.go
136 lines (112 loc) · 3.56 KB
/
stacktrace.go
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)
}