/
stack.go
197 lines (174 loc) · 4.77 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
// Package stack provides utilities to capture and pass around stack traces.
//
// This is useful for building errors that know where they originated from, to
// track where a certain log event occured and so on.
package stack
import (
"bytes"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
)
const maxStackSize = 32
// Frame identifies a file, line & function name in the stack.
type Frame struct {
File string
Line int
Name string
}
// String provides the standard file:line representation.
func (f Frame) String() string {
return fmt.Sprintf("%s:%d %s", f.File, f.Line, f.Name)
}
// Stack represents an ordered set of Frames.
type Stack []Frame
// String provides the standard multi-line stack trace.
func (s Stack) String() string {
var b bytes.Buffer
writeStack(&b, s)
return b.String()
}
// Multi represents a number of Stacks. This is useful to allow tracking a
// value as it travels thru code.
type Multi struct {
stacks []Stack
}
// Stacks returns the tracked Stacks.
func (m *Multi) Stacks() []Stack {
return m.stacks
}
// Add the given Stack to this Multi.
func (m *Multi) Add(s Stack) {
m.stacks = append(m.stacks, s)
}
// AddCallers adds the Callers Stack to this Multi. The argument skip is
// the number of stack frames to ascend, with 0 identifying the caller of
// Callers.
func (m *Multi) AddCallers(skip int) {
m.Add(Callers(skip + 1))
}
// String provides a human readable multi-line stack trace.
func (m *Multi) String() string {
var b bytes.Buffer
for i, s := range m.stacks {
if i != 0 {
fmt.Fprintf(&b, "\n(Stack %d)\n", i+1)
}
writeStack(&b, s)
}
return b.String()
}
// Caller returns a single Frame for the caller. The argument skip is the
// number of stack frames to ascend, with 0 identifying the caller of Callers.
func Caller(skip int) Frame {
pc, file, line, _ := runtime.Caller(skip + 1)
fun := runtime.FuncForPC(pc)
return Frame{
File: StripGOPATH(file),
Line: line,
Name: StripPackage(fun.Name()),
}
}
// Callers returns a Stack of Frames for the callers. The argument skip is the
// number of stack frames to ascend, with 0 identifying the caller of Callers.
func Callers(skip int) Stack {
pcs := make([]uintptr, maxStackSize)
num := runtime.Callers(skip+2, pcs)
stack := make(Stack, num)
for i, pc := range pcs[:num] {
fun := runtime.FuncForPC(pc)
file, line := fun.FileLine(pc)
stack[i].File = StripGOPATH(file)
stack[i].Line = line
stack[i].Name = StripPackage(fun.Name())
}
return stack
}
// CallersMulti returns a Multi which includes one Stack for the
// current callers. The argument skip is the number of stack frames to ascend,
// with 0 identifying the caller of CallersMulti.
func CallersMulti(skip int) *Multi {
m := new(Multi)
m.AddCallers(skip + 1)
return m
}
func writeStack(b *bytes.Buffer, s Stack) {
var width int
for _, f := range s {
if l := len(f.File) + numDigits(f.Line) + 1; l > width {
width = l
}
}
last := len(s) - 1
for i, f := range s {
b.WriteString(f.File)
b.WriteRune(rune(':'))
n, _ := fmt.Fprintf(b, "%d", f.Line)
for i := width - len(f.File) - n; i != 0; i-- {
b.WriteRune(rune(' '))
}
b.WriteString(f.Name)
if i != last {
b.WriteRune(rune('\n'))
}
}
}
func numDigits(i int) int {
var n int
for {
n++
i = i / 10
if i == 0 {
return n
}
}
}
// This can be set by a build script. It will be the colon separated equivalent
// of the environment variable.
var gopath string
// This is the processed version based on either the above variable set by the
// build or from the GOPATH environment variable.
var gopaths []string
func init() {
// prefer the variable set at build time, otherwise fallback to the
// environment variable.
if gopath == "" {
gopath = os.Getenv("GOPATH")
}
for _, p := range strings.Split(gopath, ":") {
if p != "" {
gopaths = append(gopaths, filepath.Join(p, "src")+"/")
}
}
// Also strip GOROOT for maximum cleanliness
gopaths = append(gopaths, filepath.Join(runtime.GOROOT(), "src", "pkg")+"/")
}
// StripGOPATH strips the GOPATH prefix from the file path f.
// In development, this will be done using the GOPATH environment variable.
// For production builds, where the GOPATH environment will not be set, the
// GOPATH can be included in the binary by passing ldflags, for example:
//
// GO_LDFLAGS="$GO_LDFLAGS -X github.com/facebookgo/stack.gopath $GOPATH"
// go install "-ldflags=$GO_LDFLAGS" my/pkg
func StripGOPATH(f string) string {
for _, p := range gopaths {
if strings.HasPrefix(f, p) {
return f[len(p):]
}
}
return f
}
// StripPackage strips the package name from the given Func.Name.
func StripPackage(n string) string {
slashI := strings.LastIndex(n, "/")
if slashI == -1 {
slashI = 0 // for built-in packages
}
dotI := strings.Index(n[slashI:], ".")
if dotI == -1 {
return n
}
return n[slashI+dotI+1:]
}