-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathcallstack.go
163 lines (140 loc) · 3.65 KB
/
callstack.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
package failure
import (
"fmt"
"path"
"path/filepath"
"runtime"
"strings"
)
// CallStack represents a stack of program counters.
// It implements the Field interface.
type CallStack []uintptr
// NewCallStack creates a new CallStack from the provided program counters.
func NewCallStack(pcs []uintptr) CallStack {
return CallStack(pcs)
}
// Callers returns a CallStack of the caller's goroutine stack. The skip
// parameter determines the number of stack frames to skip before capturing the
// CallStack.
func Callers(skip int) CallStack {
var pcs [32]uintptr
n := runtime.Callers(skip+2, pcs[:])
return NewCallStack(pcs[:n])
}
// SetErrorField implements the Field interface.
func (cs CallStack) SetErrorField(setter FieldSetter) {
setter.Set(KeyCallStack, cs)
}
// HeadFrame is a method of CallStack that returns the first frame in the
// CallStack. If the CallStack is empty, it returns an empty frame.
func (cs CallStack) HeadFrame() Frame {
if len(cs) == 0 {
return emptyFrame
}
rfs := runtime.CallersFrames(cs[:1])
f, _ := rfs.Next()
return Frame{f.File, f.Line, f.Function, f.PC}
}
// Frames is a method of CallStack that returns a slice of Frame objects
// representing the CallStack's frames.
func (cs CallStack) Frames() []Frame {
if len(cs) == 0 {
return nil
}
rfs := runtime.CallersFrames(cs)
fs := make([]Frame, 0, len(cs))
for {
f, more := rfs.Next()
fs = append(fs, Frame{f.File, f.Line, f.Function, f.PC})
if !more {
break
}
}
return fs
}
// Format implements the fmt.Formatter interface.
func (cs CallStack) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case s.Flag('+'):
for _, f := range cs.Frames() {
fmt.Fprintf(s, "%+v\n", f)
}
case s.Flag('#'):
fmt.Fprintf(s, "%#v", cs.Frames())
default:
fs := cs.Frames()
l := len(fs)
if l == 0 {
return
}
for _, f := range fs[:l-1] {
fmt.Fprintf(s, "%s.%s: ", f.Pkg(), f.Func())
}
fmt.Fprintf(s, "%v", fs[l-1].Func())
}
case 's':
fmt.Fprintf(s, "%v", cs)
}
}
var emptyFrame = Frame{"???", 0, "???", uintptr(0)}
// Frame represents a single frame in a CallStack.
type Frame struct {
file string
line int
function string
pc uintptr
}
// Path returns the full path of the file associated with the Frame.
func (f Frame) Path() string {
return f.file
}
// File returns the base file name associated with the Frame.
func (f Frame) File() string {
return filepath.Base(f.file)
}
// Line returns the line number of the file.
func (f Frame) Line() int {
return f.line
}
// Func returns the function name associated with the Frame.
func (f Frame) Func() string {
fs := strings.Split(path.Base(f.function), ".")
if len(fs) >= 1 {
return strings.Join(fs[1:], ".")
}
return fs[0]
}
// PkgPath returns the package path associated with the Frame.
func (f Frame) PkgPath() string {
// e.g.
// When f.function = github.com/morikuni/failure_test.TestFrame.func1.1
// f.PkgPath() = github.com/morikuni/failure_test
lastSlash := strings.LastIndex(f.function, "/")
if lastSlash == -1 {
lastSlash = 0
}
return f.function[:strings.Index(f.function[lastSlash:], ".")+lastSlash]
}
// PC returns the program counter associated with the Frame
func (f Frame) PC() uintptr {
return f.pc
}
// Pkg returns the package name associated with the Frame.
// It is the last element of the PkgPath.
func (f Frame) Pkg() string {
return path.Base(f.PkgPath())
}
// Format implements the fmt.Formatter interface.
func (f Frame) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "[%s.%s] ", f.Pkg(), f.Func())
}
fallthrough
case 's':
fmt.Fprintf(s, "%s:%d", f.Path(), f.Line())
}
}