forked from mongodb/grip
/
stack.go
212 lines (174 loc) · 5.7 KB
/
stack.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
/*
Stack Messages
The Stack message Composer implementations capture a full stacktrace
information during message construction, and attach a message to that
trace. The string form of the message includes the package and file
name and line number of the last call site, while the Raw form of the
message includes the entire stack. Use with an appropriate sender to
capture the desired output.
All stack message constructors take a "skip" parameter which tells how
many stack frames to skip relative to the invocation of the
constructor. Skip values less than or equal to 0 become 1, and are
equal the call site of the constructor, use larger numbers if you're
wrapping these constructors in our own infrastructure.
In general Composers are lazy, and defer work until the message is
being sent; however, the stack Composers must capture the stack when
they're called rather than when they're sent to produce meaningful
data.
*/
package message
import (
"fmt"
"go/build"
"path/filepath"
"runtime"
"strings"
"github.com/mongodb/grip/level"
)
const maxLevels = 1024
// types are internal, and exposed only via the composer interface.
type stackMessage struct {
Composer
tagged bool
trace stackFrames
}
// StackFrame captures a single item in a stack trace, and is used
// internally and in the StackTrace output.
type StackFrame struct {
Function string `bson:"function" json:"function" yaml:"function"`
File string `bson:"file" json:"file" yaml:"file"`
Line int `bson:"line" json:"line" yaml:"line"`
}
// StackTrace structs are returned by the Raw method of the stackMessage type
type StackTrace struct {
Context interface{} `bson:"context,omitempty" json:"context,omitempty" yaml:"context,omitempty"`
Frames stackFrames `bson:"frames" json:"frames" yaml:"frames"`
}
func (s StackTrace) String() string { return stackFrames(s.Frames).String() }
////////////////////////////////////////////////////////////////////////
//
// Constructors for stack frame messages.
//
////////////////////////////////////////////////////////////////////////
// WrapStack annotates a message, converted to a composer using the
// normal rules if needed, with a stack trace. Use the skip argument to
// skip frames if your embedding this in your own wrapper or wrappers.
func WrapStack(skip int, msg interface{}) Composer {
return &stackMessage{
trace: captureStack(skip),
Composer: ConvertToComposer(level.Priority(0), msg),
}
}
// NewStack builds a Composer implementation that captures the current
// stack trace with a single string message. Use the skip argument to
// skip frames if your embedding this in your own wrapper or wrappers.
func NewStack(skip int, message string) Composer {
return &stackMessage{
trace: captureStack(skip),
Composer: NewString(message),
}
}
// NewStackLines returns a composer that builds a fmt.Println style
// message that also captures a stack trace. Use the skip argument to
// skip frames if your embedding this in your own wrapper or wrappers.
func NewStackLines(skip int, messages ...interface{}) Composer {
return &stackMessage{
trace: captureStack(skip),
Composer: NewLine(messages...),
}
}
// NewStackFormatted returns a composer that builds a fmt.Printf style
// message that also captures a stack trace. Use the skip argument to
// skip frames if your embedding this in your own wrapper or wrappers.
func NewStackFormatted(skip int, message string, args ...interface{}) Composer {
return &stackMessage{
trace: captureStack(skip),
Composer: NewFormatted(message, args...),
}
}
////////////////////////////////////////////////////////////////////////
//
// Implementation of Composer methods not implemented by Base
//
////////////////////////////////////////////////////////////////////////
func (m *stackMessage) String() string {
return strings.Trim(strings.Join([]string{m.getTag(), m.Composer.String()}, " "), " \n\t")
}
func (m *stackMessage) Raw() interface{} {
switch payload := m.Composer.(type) {
case *fieldMessage:
payload.fields["stack.frames"] = m.trace
return payload
default:
return StackTrace{
Context: payload,
Frames: m.trace,
}
}
}
////////////////////////////////////////////////////////////////////////
//
// Internal Operations for Collecting and processing data.
//
////////////////////////////////////////////////////////////////////////
type stackFrames []StackFrame
func (f stackFrames) String() string {
out := make([]string, len(f))
for idx, frame := range f {
out[idx] = frame.String()
}
return strings.Join(out, " ")
}
func (f StackFrame) String() string {
if strings.HasPrefix(f.File, build.Default.GOPATH) {
funcNameParts := strings.Split(f.Function, ".")
var fname string
if len(funcNameParts) > 0 {
fname = funcNameParts[len(funcNameParts)-1]
} else {
fname = f.Function
}
return fmt.Sprintf("%s:%d (%s)",
f.File[len(build.Default.GOPATH)+5:],
f.Line,
fname)
}
if strings.HasPrefix(f.File, build.Default.GOROOT) {
return fmt.Sprintf("%s:%d",
f.File[len(build.Default.GOROOT)+5:],
f.Line)
}
dir, fileName := filepath.Split(f.File)
return fmt.Sprintf("%s:%d",
filepath.Join(filepath.Base(dir), fileName),
f.Line)
}
func captureStack(skip int) []StackFrame {
if skip <= 0 {
// don't recorded captureStack
skip = 1
}
// captureStack is always called by a constructor, so we need
// to bump it again
skip++
trace := []StackFrame{}
for i := 0; i < maxLevels; i++ {
pc, file, line, ok := runtime.Caller(skip)
if !ok {
break
}
trace = append(trace, StackFrame{
Function: runtime.FuncForPC(pc).Name(),
File: file,
Line: line})
skip++
}
return trace
}
func (m *stackMessage) getTag() string {
if len(m.trace) >= 1 {
m.tagged = true
return m.trace[0].String()
}
return ""
}