1
1
package errors
2
2
3
3
import (
4
+ "bytes"
4
5
"fmt"
5
6
"io"
6
7
"path"
7
8
"runtime"
9
+ "strconv"
8
10
"strings"
9
11
)
10
12
@@ -24,33 +26,40 @@ type Frame runtime.Frame
24
26
// GOPATH separated by \n\t (<funcname>\n\t<path>)
25
27
// %+v equivalent to %+s:%d
26
28
func (f Frame ) Format (s fmt.State , verb rune ) {
29
+ f .format (s , s , verb )
30
+ }
31
+
32
+ // format allows stack trace printing calls to be made with a bytes.Buffer.
33
+ func (f Frame ) format (w io.Writer , s fmt.State , verb rune ) {
27
34
switch verb {
28
35
case 's' :
29
36
switch {
30
37
case s .Flag ('+' ):
31
38
fn := runtime .Frame (f ).Func
32
39
if fn == nil {
33
- io .WriteString (s , "unknown" )
40
+ io .WriteString (w , "unknown" )
34
41
} else {
35
42
file := runtime .Frame (f ).File
36
- fmt .Fprintf (s , "%s\n \t %s" , fn .Name (), file )
43
+ io .WriteString (w , fn .Name ())
44
+ io .WriteString (w , "\n \t " )
45
+ io .WriteString (w , file )
37
46
}
38
47
default :
39
48
file := runtime .Frame (f ).File
40
49
if file == "" {
41
50
file = "unknown"
42
51
}
43
- io .WriteString (s , path .Base (file ))
52
+ io .WriteString (w , path .Base (file ))
44
53
}
45
54
case 'd' :
46
- fmt . Fprintf ( s , "%d" , runtime .Frame (f ).Line )
55
+ io . WriteString ( w , strconv . Itoa ( runtime .Frame (f ).Line ) )
47
56
case 'n' :
48
57
name := runtime .Frame (f ).Function
49
58
io .WriteString (s , funcname (name ))
50
59
case 'v' :
51
- f .Format ( s , 's' )
52
- io .WriteString (s , ":" )
53
- f .Format ( s , 'd' )
60
+ f .format ( w , s , 's' )
61
+ io .WriteString (w , ":" )
62
+ f .format ( w , s , 'd' )
54
63
}
55
64
}
56
65
@@ -66,23 +75,50 @@ type StackTrace []Frame
66
75
//
67
76
// %+v Prints filename, function, and line number for each Frame in the stack.
68
77
func (st StackTrace ) Format (s fmt.State , verb rune ) {
78
+ var b bytes.Buffer
69
79
switch verb {
70
80
case 'v' :
71
81
switch {
72
82
case s .Flag ('+' ):
73
- for _ , f := range st {
74
- fmt .Fprintf (s , "\n %+v" , f )
83
+ b .Grow (len (st ) * stackMinLen )
84
+ for _ , fr := range st {
85
+ b .WriteByte ('\n' )
86
+ fr .format (& b , s , verb )
75
87
}
76
88
case s .Flag ('#' ):
77
- fmt .Fprintf (s , "%#v" , []Frame (st ))
89
+ fmt .Fprintf (& b , "%#v" , []Frame (st ))
78
90
default :
79
- fmt . Fprintf ( s , "%v" , [] Frame ( st ) )
91
+ st . formatSlice ( & b , s , verb )
80
92
}
81
93
case 's' :
82
- fmt .Fprintf (s , "%s" , []Frame (st ))
94
+ st .formatSlice (& b , s , verb )
95
+ }
96
+ io .Copy (s , & b )
97
+ }
98
+
99
+ // formatSlice will format this StackTrace into the given buffer as a slice of
100
+ // Frame, only valid when called with '%s' or '%v'.
101
+ func (st StackTrace ) formatSlice (b * bytes.Buffer , s fmt.State , verb rune ) {
102
+ b .WriteByte ('[' )
103
+ if len (st ) == 0 {
104
+ b .WriteByte (']' )
105
+ return
106
+ }
107
+
108
+ b .Grow (len (st ) * (stackMinLen / 4 ))
109
+ st [0 ].format (b , s , verb )
110
+ for _ , fr := range st [1 :] {
111
+ b .WriteByte (' ' )
112
+ fr .format (b , s , verb )
83
113
}
114
+ b .WriteByte (']' )
84
115
}
85
116
117
+ // stackMinLen is a best-guess at the minimum length of a stack trace. It
118
+ // doesn't need to be exact, just give a good enough head start for the buffer
119
+ // to avoid the expensive early growth.
120
+ const stackMinLen = 96
121
+
86
122
// stack represents a stack of program counters.
87
123
type stack []uintptr
88
124
0 commit comments