11package errors
22
33import (
4+ "bytes"
45 "fmt"
56 "io"
67 "path"
78 "runtime"
9+ "strconv"
810 "strings"
911)
1012
@@ -72,30 +74,37 @@ func (f Frame) line() int {
7274// GOPATH separated by \n\t (<funcname>\n\t<path>)
7375// %+v equivalent to %+s:%d
7476func (f Frame ) Format (s fmt.State , verb rune ) {
77+ f .format (s , s , verb )
78+ }
79+
80+ // format allows stack trace printing calls to be made with a bytes.Buffer.
81+ func (f Frame ) format (w io.Writer , s fmt.State , verb rune ) {
7582 switch verb {
7683 case 's' :
7784 switch {
7885 case s .Flag ('+' ):
7986 pc := f .pc ()
8087 fn := runtime .FuncForPC (pc )
8188 if fn == nil {
82- io .WriteString (s , "unknown" )
89+ io .WriteString (w , "unknown" )
8390 } else {
8491 file , _ := fn .FileLine (pc )
85- fmt .Fprintf (s , "%s\n \t %s" , fn .Name (), file )
92+ io .WriteString (w , fn .Name ())
93+ io .WriteString (w , "\n \t " )
94+ io .WriteString (w , file )
8695 }
8796 default :
88- io .WriteString (s , path .Base (f .file ()))
97+ io .WriteString (w , path .Base (f .file ()))
8998 }
9099 case 'd' :
91- fmt . Fprintf ( s , "%d" , f .line ())
100+ io . WriteString ( w , strconv . Itoa ( f .line () ))
92101 case 'n' :
93102 name := runtime .FuncForPC (f .pc ()).Name ()
94- io .WriteString (s , funcname (name ))
103+ io .WriteString (w , funcname (name ))
95104 case 'v' :
96- f .Format ( s , 's' )
97- io .WriteString (s , ":" )
98- f .Format ( s , 'd' )
105+ f .format ( w , s , 's' )
106+ io .WriteString (w , ":" )
107+ f .format ( w , s , 'd' )
99108 }
100109}
101110
@@ -111,23 +120,50 @@ type StackTrace []Frame
111120//
112121// %+v Prints filename, function, and line number for each Frame in the stack.
113122func (st StackTrace ) Format (s fmt.State , verb rune ) {
123+ var b bytes.Buffer
114124 switch verb {
115125 case 'v' :
116126 switch {
117127 case s .Flag ('+' ):
118- for _ , f := range st {
119- fmt .Fprintf (s , "\n %+v" , f )
128+ b .Grow (len (st ) * stackMinLen )
129+ for _ , fr := range st {
130+ b .WriteByte ('\n' )
131+ fr .format (& b , s , verb )
120132 }
121133 case s .Flag ('#' ):
122- fmt .Fprintf (s , "%#v" , []Frame (st ))
134+ fmt .Fprintf (& b , "%#v" , []Frame (st ))
123135 default :
124- fmt . Fprintf ( s , "%v" , [] Frame ( st ) )
136+ st . formatSlice ( & b , s , verb )
125137 }
126138 case 's' :
127- fmt .Fprintf (s , "%s" , []Frame (st ))
139+ st .formatSlice (& b , s , verb )
140+ }
141+ io .Copy (s , & b )
142+ }
143+
144+ // formatSlice will format this StackTrace into the given buffer as a slice of
145+ // Frame, only valid when called with '%s' or '%v'.
146+ func (st StackTrace ) formatSlice (b * bytes.Buffer , s fmt.State , verb rune ) {
147+ b .WriteByte ('[' )
148+ if len (st ) == 0 {
149+ b .WriteByte (']' )
150+ return
151+ }
152+
153+ b .Grow (len (st ) * (stackMinLen / 4 ))
154+ st [0 ].format (b , s , verb )
155+ for _ , fr := range st [1 :] {
156+ b .WriteByte (' ' )
157+ fr .format (b , s , verb )
128158 }
159+ b .WriteByte (']' )
129160}
130161
162+ // stackMinLen is a best-guess at the minimum length of a stack trace. It
163+ // doesn't need to be exact, just give a good enough head start for the buffer
164+ // to avoid the expensive early growth.
165+ const stackMinLen = 96
166+
131167// stack represents a stack of program counters.
132168type stack []uintptr
133169
@@ -136,10 +172,14 @@ func (s *stack) Format(st fmt.State, verb rune) {
136172 case 'v' :
137173 switch {
138174 case st .Flag ('+' ):
175+ var b bytes.Buffer
176+ b .Grow (len (* s ) * stackMinLen )
139177 for _ , pc := range * s {
140178 f := Frame (pc )
141- fmt .Fprintf (st , "\n %+v" , f )
179+ b .WriteByte ('\n' )
180+ f .format (& b , st , 'v' )
142181 }
182+ io .Copy (st , & b )
143183 }
144184 }
145185}
0 commit comments