/
caller.go
158 lines (133 loc) · 2.89 KB
/
caller.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
package xo
import (
"fmt"
"io"
"runtime"
"strconv"
"strings"
)
// Caller describes a caller.
type Caller struct {
Short string
Full string
File string
Line int
Stack []uintptr
}
// GetCaller returns information on the caller.
func GetCaller(skip, limit int) Caller {
// ensure limit
if limit == 0 {
limit = 32
}
// get stack frames
stack := make([]uintptr, limit)
n := runtime.Callers(skip+2, stack)
// prepare caller
caller := Caller{
Stack: stack[:n],
}
// analyze caller
caller.Analyze()
return caller
}
// Analyze will fill Short, Full, File and Line from the first stack frame.
func (c *Caller) Analyze() {
// check stack
if len(c.Stack) == 0 {
return
}
// get first frame
frame, _ := runtime.CallersFrames(c.Stack).Next()
// get name, file and line
name := frame.Function
file := frame.File
line := frame.Line
// get short name
short := name
if idx := strings.LastIndex(short, "/"); idx > 0 {
short = short[idx+1:]
}
// set names
c.Short = short
c.Full = name
c.File = file
c.Line = line
}
// Drop will drop the specified amount of frames from the caller.
func (c *Caller) Drop(n int) {
// check length
if len(c.Stack)-n <= 0 {
return
}
// drop frames
c.Stack = c.Stack[n:]
// re-analyze
c.Analyze()
}
// Includes returns whether the receiver fully includes the provided caller.
// Ignore can be set to ignore n bottom frames. Two adjacent callers will have
// the same stack except for the last frame which represents the call site.
func (c Caller) Includes(cc Caller, ignore int) bool {
// get lengths
cl := len(c.Stack)
ccl := len(cc.Stack)
// check length
if cl < ccl {
return false
}
// prepare depth
depth := ccl - ignore
// reverse compare stacks
for i := 0; i < depth; i++ {
if c.Stack[cl-1-i] != cc.Stack[ccl-1-i] {
return false
}
}
return true
}
// String will format the caller as a string.
func (c Caller) String() string {
return c.Short
}
// Format will format the caller.
//
// %s short name
// %q "short name"
// %v full name
// %+v stack trace
func (c Caller) Format(s fmt.State, verb rune) {
if verb == 's' {
check(io.WriteString(s, c.Short))
} else if verb == 'q' {
check(fmt.Fprintf(s, "%q", c.Short))
} else if verb == 'v' {
if s.Flag('+') {
c.Print(s)
} else {
check(io.WriteString(s, c.Full))
}
}
}
// Print will print the stack to the provided writer.
func (c Caller) Print(out io.Writer) {
// get frames
frames := runtime.CallersFrames(c.Stack)
// iterate frames
var frame runtime.Frame
more := true
for more {
// get next frame
frame, more = frames.Next()
// print frame
check(io.WriteString(out, "> "))
check(io.WriteString(out, frame.Function))
check(io.WriteString(out, "\n> \t"))
check(io.WriteString(out, frame.File))
check(io.WriteString(out, ":"))
check(io.WriteString(out, strconv.Itoa(frame.Line)))
if more {
check(io.WriteString(out, "\n"))
}
}
}