-
-
Notifications
You must be signed in to change notification settings - Fork 13
/
stacktrace.go
142 lines (113 loc) · 3.25 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
137
138
139
140
141
142
package oops
import (
"fmt"
"runtime"
"runtime/debug"
"strings"
)
///
/// Inspired by palantir/stacktrace repo
/// -> https://github.com/palantir/stacktrace/blob/master/stacktrace.go
/// -> Apache 2.0 LICENSE
///
var (
StackTraceMaxDepth int = 10
buildInfo, _ = debug.ReadBuildInfo()
)
type oopsStacktraceFrame struct {
pc uintptr
file string
function string
line int
}
func (frame *oopsStacktraceFrame) String() string {
currentFrame := fmt.Sprintf("%v:%v", frame.file, frame.line)
if frame.function != "" {
currentFrame = fmt.Sprintf("%v:%v %v()", frame.file, frame.line, frame.function)
}
return currentFrame
}
type oopsStacktrace struct {
span string
frames []oopsStacktraceFrame
}
func (st *oopsStacktrace) Error() string {
return st.String("")
}
func (st *oopsStacktrace) String(deepestFrame string) string {
var str string
newline := func() {
if str != "" && !strings.HasSuffix(str, "\n") {
str += "\n"
}
}
for _, frame := range st.frames {
if frame.file != "" {
currentFrame := frame.String()
if currentFrame == deepestFrame {
break
}
newline()
str += " --- at " + currentFrame
}
}
return str
}
func (st *oopsStacktrace) Source() (string, []string) {
if len(st.frames) == 0 {
return "", []string{}
}
firstFrame := st.frames[0]
header := firstFrame.String()
body := getSourceFromFrame(firstFrame)
return header, body
}
func newStacktrace(span string) *oopsStacktrace {
frames := []oopsStacktraceFrame{}
// We loop until we have StackTraceMaxDepth frames or we run out of frames.
// Frames from this package are skipped.
for i := 0; len(frames) < StackTraceMaxDepth; i++ {
pc, file, line, ok := runtime.Caller(i)
if !ok {
break
}
file = removeGoPath(file)
f := runtime.FuncForPC(pc)
if f == nil {
break
}
function := shortFuncName(f)
packageName := buildInfo.Path
packageNameExamples := packageName + "/examples/"
isGoPkg := len(runtime.GOROOT()) > 0 && strings.Contains(file, runtime.GOROOT()) // skip frames in GOROOT if it's set
isOopsPkg := strings.Contains(file, packageName) // skip frames in this package
isExamplePkg := strings.Contains(file, packageNameExamples) // do not skip frames in this package examples
isTestPkg := strings.Contains(file, "_test.go") // do not skip frames in tests
if !isGoPkg && (!isOopsPkg || isExamplePkg || isTestPkg) {
frames = append(frames, oopsStacktraceFrame{
pc: pc,
file: file,
function: function,
line: line,
})
}
}
return &oopsStacktrace{
span: span,
frames: frames,
}
}
func shortFuncName(f *runtime.Func) string {
// f.Name() is like one of these:
// - "github.com/palantir/shield/package.FuncName"
// - "github.com/palantir/shield/package.Receiver.MethodName"
// - "github.com/palantir/shield/package.(*PtrReceiver).MethodName"
longName := f.Name()
withoutPath := longName[strings.LastIndex(longName, "/")+1:]
withoutPackage := withoutPath[strings.Index(withoutPath, ".")+1:]
shortName := withoutPackage
shortName = strings.Replace(shortName, "(", "", 1)
shortName = strings.Replace(shortName, "*", "", 1)
shortName = strings.Replace(shortName, ")", "", 1)
return shortName
}