11package errors
22
33import (
4+ "bytes"
45 "fmt"
56 "io"
67 "path"
78 "runtime"
9+ "strconv"
810 "strings"
911)
1012
@@ -50,30 +52,37 @@ func (f Frame) line() int {
5052// GOPATH separated by \n\t (<funcname>\n\t<path>)
5153// %+v equivalent to %+s:%d
5254func (f Frame ) Format (s fmt.State , verb rune ) {
55+ f .format (s , s , verb )
56+ }
57+
58+ // format allows stack trace printing calls to be made with a bytes.Buffer.
59+ func (f Frame ) format (w io.Writer , s fmt.State , verb rune ) {
5360 switch verb {
5461 case 's' :
5562 switch {
5663 case s .Flag ('+' ):
5764 pc := f .pc ()
5865 fn := runtime .FuncForPC (pc )
5966 if fn == nil {
60- io .WriteString (s , "unknown" )
67+ io .WriteString (w , "unknown" )
6168 } else {
6269 file , _ := fn .FileLine (pc )
63- fmt .Fprintf (s , "%s\n \t %s" , fn .Name (), file )
70+ io .WriteString (w , fn .Name ())
71+ io .WriteString (w , "\n \t " )
72+ io .WriteString (w , file )
6473 }
6574 default :
66- io .WriteString (s , path .Base (f .file ()))
75+ io .WriteString (w , path .Base (f .file ()))
6776 }
6877 case 'd' :
69- fmt . Fprintf ( s , "%d" , f .line ())
78+ io . WriteString ( w , strconv . Itoa ( f .line () ))
7079 case 'n' :
7180 name := runtime .FuncForPC (f .pc ()).Name ()
72- io .WriteString (s , funcname (name ))
81+ io .WriteString (w , funcname (name ))
7382 case 'v' :
74- f .Format ( s , 's' )
75- io .WriteString (s , ":" )
76- f .Format ( s , 'd' )
83+ f .format ( w , s , 's' )
84+ io .WriteString (w , ":" )
85+ f .format ( w , s , 'd' )
7786 }
7887}
7988
@@ -89,23 +98,50 @@ type StackTrace []Frame
8998//
9099// %+v Prints filename, function, and line number for each Frame in the stack.
91100func (st StackTrace ) Format (s fmt.State , verb rune ) {
101+ var b bytes.Buffer
92102 switch verb {
93103 case 'v' :
94104 switch {
95105 case s .Flag ('+' ):
96- for _ , f := range st {
97- fmt .Fprintf (s , "\n %+v" , f )
106+ b .Grow (len (st ) * stackMinLen )
107+ for _ , fr := range st {
108+ b .WriteByte ('\n' )
109+ fr .format (& b , s , verb )
98110 }
99111 case s .Flag ('#' ):
100- fmt .Fprintf (s , "%#v" , []Frame (st ))
112+ fmt .Fprintf (& b , "%#v" , []Frame (st ))
101113 default :
102- fmt . Fprintf ( s , "%v" , [] Frame ( st ) )
114+ st . formatSlice ( & b , s , verb )
103115 }
104116 case 's' :
105- fmt .Fprintf (s , "%s" , []Frame (st ))
117+ st .formatSlice (& b , s , verb )
118+ }
119+ io .Copy (s , & b )
120+ }
121+
122+ // formatSlice will format this StackTrace into the given buffer as a slice of
123+ // Frame, only valid when called with '%s' or '%v'.
124+ func (st StackTrace ) formatSlice (b * bytes.Buffer , s fmt.State , verb rune ) {
125+ b .WriteByte ('[' )
126+ if len (st ) == 0 {
127+ b .WriteByte (']' )
128+ return
129+ }
130+
131+ b .Grow (len (st ) * (stackMinLen / 4 ))
132+ st [0 ].format (b , s , verb )
133+ for _ , fr := range st [1 :] {
134+ b .WriteByte (' ' )
135+ fr .format (b , s , verb )
106136 }
137+ b .WriteByte (']' )
107138}
108139
140+ // stackMinLen is a best-guess at the minimum length of a stack trace. It
141+ // doesn't need to be exact, just give a good enough head start for the buffer
142+ // to avoid the expensive early growth.
143+ const stackMinLen = 96
144+
109145// stack represents a stack of program counters.
110146type stack []uintptr
111147
@@ -114,10 +150,14 @@ func (s *stack) Format(st fmt.State, verb rune) {
114150 case 'v' :
115151 switch {
116152 case st .Flag ('+' ):
153+ var b bytes.Buffer
154+ b .Grow (len (* s ) * stackMinLen )
117155 for _ , pc := range * s {
118156 f := Frame (pc )
119- fmt .Fprintf (st , "\n %+v" , f )
157+ b .WriteByte ('\n' )
158+ f .format (& b , st , 'v' )
120159 }
160+ io .Copy (st , & b )
121161 }
122162 }
123163}
0 commit comments