@@ -10,6 +10,7 @@ import (
10
10
"go/build"
11
11
"io/ioutil"
12
12
"path/filepath"
13
+ "reflect"
13
14
"runtime"
14
15
"strings"
15
16
"sync"
@@ -52,35 +53,63 @@ type StacktraceFrame struct {
52
53
InApp bool `json:"in_app"`
53
54
}
54
55
55
- // GetOrNewStacktrace tries to get stacktrace from err as an interface of github.com/pkg/errors, or else NewStacktrace()
56
- func GetOrNewStacktrace (err error , skip int , context int , appPackagePrefixes []string ) * Stacktrace {
57
- type stackTracer interface {
58
- StackTrace () []runtime.Frame
59
- }
60
- stacktrace , ok := err .(stackTracer )
61
- if ! ok {
62
- return NewStacktrace (skip + 1 , context , appPackagePrefixes )
63
- }
56
+ // extractFramesFromPcs translates slice of stack trace pointers into usable frames
57
+ func extractFramesFromPcs (pcs []uintptr , context int , appPackagePrefixes []string ) []* StacktraceFrame {
64
58
var frames []* StacktraceFrame
65
- for f := range stacktrace .StackTrace () {
66
- pc := uintptr (f ) - 1
67
- fn := runtime .FuncForPC (pc )
68
- var fName string
69
- var file string
70
- var line int
71
- if fn != nil {
72
- file , line = fn .FileLine (pc )
73
- fName = fn .Name ()
74
- } else {
75
- file = "unknown"
76
- fName = "unknown"
77
- }
78
- frame := NewStacktraceFrame (pc , fName , file , line , context , appPackagePrefixes )
59
+ callersFrames := runtime .CallersFrames (pcs )
60
+
61
+ for {
62
+ fr , more := callersFrames .Next ()
63
+ frame := NewStacktraceFrame (fr .PC , fr .Function , fr .File , fr .Line , context , appPackagePrefixes )
79
64
if frame != nil {
80
65
frames = append ([]* StacktraceFrame {frame }, frames ... )
81
66
}
67
+ if ! more {
68
+ break
69
+ }
70
+ }
71
+
72
+ return frames
73
+ }
74
+
75
+ // GetOrNewStacktrace tries to get stacktrace from err as an interface of github.com/pkg/errors, or else NewStacktrace()
76
+ // Use of reflection allows us to not have a hard dependency on any given package, so we don't have to import it
77
+ func GetOrNewStacktrace (err error , skip int , context int , appPackagePrefixes []string ) * Stacktrace {
78
+ // https://github.com/pkg/errors
79
+ // https://github.com/pingcap/errors
80
+ methodStackTrace := reflect .ValueOf (err ).MethodByName ("StackTrace" )
81
+ // https://github.com/go-errors/errors
82
+ methodStackFrames := reflect .ValueOf (err ).MethodByName ("StackFrames" )
83
+
84
+ if methodStackTrace .IsValid () {
85
+ stacktrace := methodStackTrace .Call (make ([]reflect.Value , 0 ))[0 ]
86
+ if stacktrace .Kind () != reflect .Slice {
87
+ return NewStacktrace (skip + 1 , context , appPackagePrefixes )
88
+ }
89
+
90
+ pcs := make ([]uintptr , stacktrace .Len ())
91
+ for i := 0 ; i < stacktrace .Len (); i ++ {
92
+ pcs = append (pcs , uintptr (stacktrace .Index (i ).Uint ()))
93
+ }
94
+ frames := extractFramesFromPcs (pcs , context , appPackagePrefixes )
95
+
96
+ return & Stacktrace {frames }
97
+ } else if methodStackFrames .IsValid () {
98
+ stacktrace := methodStackFrames .Call (make ([]reflect.Value , 0 ))[0 ]
99
+ if stacktrace .Kind () != reflect .Slice {
100
+ return NewStacktrace (skip + 1 , context , appPackagePrefixes )
101
+ }
102
+
103
+ pcs := make ([]uintptr , stacktrace .Len ())
104
+ for i := 0 ; i < stacktrace .Len (); i ++ {
105
+ pcs = append (pcs , uintptr (stacktrace .Index (i ).FieldByName ("ProgramCounter" ).Uint ()))
106
+ }
107
+ frames := extractFramesFromPcs (pcs , context , appPackagePrefixes )
108
+
109
+ return & Stacktrace {frames }
82
110
}
83
- return & Stacktrace {Frames : frames }
111
+
112
+ return NewStacktrace (skip + 1 , context , appPackagePrefixes )
84
113
}
85
114
86
115
// NewStacktrace intializes and populates a new stacktrace, skipping skip frames.
@@ -92,8 +121,6 @@ func GetOrNewStacktrace(err error, skip int, context int, appPackagePrefixes []s
92
121
// appPackagePrefixes is a list of prefixes used to check whether a package should
93
122
// be considered "in app".
94
123
func NewStacktrace (skip int , context int , appPackagePrefixes []string ) * Stacktrace {
95
- var frames []* StacktraceFrame
96
-
97
124
callerPcs := make ([]uintptr , 100 )
98
125
numCallers := runtime .Callers (skip + 2 , callerPcs )
99
126
@@ -102,32 +129,18 @@ func NewStacktrace(skip int, context int, appPackagePrefixes []string) *Stacktra
102
129
return nil
103
130
}
104
131
105
- callersFrames := runtime . CallersFrames (callerPcs )
132
+ frames := extractFramesFromPcs (callerPcs , context , appPackagePrefixes )
106
133
107
- for {
108
- fr , more := callersFrames .Next ()
109
- if fr .Func != nil {
110
- frame := NewStacktraceFrame (fr .PC , fr .Function , fr .File , fr .Line , context , appPackagePrefixes )
111
- if frame != nil {
112
- frames = append (frames , frame )
113
- }
114
- }
115
- if ! more {
116
- break
117
- }
118
- }
119
134
// If there are no frames, the entire stacktrace is nil
120
135
if len (frames ) == 0 {
121
136
return nil
122
137
}
138
+
123
139
// Optimize the path where there's only 1 frame
124
140
if len (frames ) == 1 {
125
141
return & Stacktrace {frames }
126
142
}
127
- // Sentry wants the frames with the oldest first, so reverse them
128
- for i , j := 0 , len (frames )- 1 ; i < j ; i , j = i + 1 , j - 1 {
129
- frames [i ], frames [j ] = frames [j ], frames [i ]
130
- }
143
+
131
144
return & Stacktrace {frames }
132
145
}
133
146
@@ -140,6 +153,14 @@ func NewStacktrace(skip int, context int, appPackagePrefixes []string) *Stacktra
140
153
// appPackagePrefixes is a list of prefixes used to check whether a package should
141
154
// be considered "in app".
142
155
func NewStacktraceFrame (pc uintptr , fName , file string , line , context int , appPackagePrefixes []string ) * StacktraceFrame {
156
+ if file == "" {
157
+ file = "unknown"
158
+ }
159
+
160
+ if fName == "" {
161
+ fName = "unknown"
162
+ }
163
+
143
164
frame := & StacktraceFrame {AbsolutePath : file , Filename : trimPath (file ), Lineno : line }
144
165
frame .Module , frame .Function = functionName (fName )
145
166
frame .InApp = isInAppFrame (* frame , appPackagePrefixes )
@@ -150,6 +171,11 @@ func NewStacktraceFrame(pc uintptr, fName, file string, line, context int, appPa
150
171
return nil
151
172
}
152
173
174
+ // Skip useless frames
175
+ if frame .Filename == "unknown" && frame .Function == "unknown" && frame .Lineno == 0 && frame .Colno == 0 {
176
+ return nil
177
+ }
178
+
153
179
if context > 0 {
154
180
contextLines , lineIdx := sourceCodeLoader .Load (file , line , context )
155
181
if len (contextLines ) > 0 {
@@ -179,11 +205,11 @@ func isInAppFrame(frame StacktraceFrame, appPackagePrefixes []string) bool {
179
205
if frame .Module == "main" {
180
206
return true
181
207
}
182
- for _ , prefix := range appPackagePrefixes {
183
- if strings .HasPrefix (frame .Module , prefix ) && ! strings .Contains (frame .Module , "vendor" ) && ! strings .Contains (frame .Module , "third_party" ) {
184
- return true
185
- }
186
- }
208
+ for _ , prefix := range appPackagePrefixes {
209
+ if strings .HasPrefix (frame .Module , prefix ) && ! strings .Contains (frame .Module , "vendor" ) && ! strings .Contains (frame .Module , "third_party" ) {
210
+ return true
211
+ }
212
+ }
187
213
return false
188
214
}
189
215
0 commit comments