-
Notifications
You must be signed in to change notification settings - Fork 1
/
frame.go
127 lines (113 loc) · 2.72 KB
/
frame.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
package errors
import (
"fmt"
"io"
"path"
"runtime"
"strconv"
"strings"
)
const fmtInline = 'i'
// Frame is a single step in stack trace.
type Frame struct {
FilePath string
Fn string
Line int
}
// String formats Frame to string.
func (f Frame) String() string {
return fmt.Sprintf("%s:%d[%s]", f.FilePath, f.Line, funcname(f.Fn))
}
// Format formats the frame according to the fmt.Formatter interface.
//
// %s source file
// %d source line
// %n function name
// %v equivalent to %s:%d
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+s function name and path of source file relative to the compile time
// GOPATH separated by \n\t (<funcname>\n\t<path>)
// %+v equivalent to %+s:%d
func (f Frame) Format(s fmt.State, verb rune) {
switch verb {
case 's':
switch {
case s.Flag('+'):
io.WriteString(s, f.String())
default:
io.WriteString(s, path.Base(f.FilePath))
}
case 'd':
io.WriteString(s, strconv.Itoa(f.Line))
case 'n':
io.WriteString(s, funcname(f.Fn))
case fmtInline:
io.WriteString(s, f.String())
case 'v':
if s.Flag('+') {
f.Format(s, 's')
return
}
io.WriteString(s, path.Base(f.FilePath))
io.WriteString(s, ":")
f.Format(s, 'd')
}
}
// StackFrames represents a slice of stack Frames in LIFO order (follows path of code to get
// the original error).
// TODO:
// 1. add String method for this slice of frames so it can be used without fuss in logging
// 2. add Formatter to be able to turn off the way it prints
type StackFrames []Frame
// String formats Frame to string.
func (f StackFrames) String() string {
var sb strings.Builder
sb.WriteString("[ ")
for i, frame := range f {
sb.WriteString(frame.String())
if i < len(f)-1 {
sb.WriteString(", ")
}
}
if sb.Len() > 2 {
sb.WriteString(" ")
}
sb.WriteString("]")
return sb.String()
}
// Format formats the frame according to the fmt.Formatter interface.
// See Frame.Format for the formatting rules.
func (f StackFrames) Format(s fmt.State, verb rune) {
io.WriteString(s, "[ ")
defer func() { io.WriteString(s, " ]") }()
for i, frame := range f {
frame.Format(s, verb)
if i < len(f)-1 {
io.WriteString(s, ", ")
}
}
}
func getFrame(skip FrameSkips) (Frame, bool) {
if skip == NoFrame {
return Frame{}, false
}
pc, path, line, ok := runtime.Caller(int(skip))
if !ok {
return Frame{}, false
}
frame := Frame{
Fn: runtime.FuncForPC(pc).Name(),
Line: line,
FilePath: path,
}
return frame, true
}
// funcname removes the path prefix component of a function's name reported by func.Name().
func funcname(name string) string {
i := strings.LastIndex(name, "/")
name = name[i+1:]
i = strings.Index(name, ".")
return name[i+1:]
}