/ vendor / github.com / go-stack / stack / stack.go
stack.go
  1  // Package stack implements utilities to capture, manipulate, and format call
  2  // stacks. It provides a simpler API than package runtime.
  3  //
  4  // The implementation takes care of the minutia and special cases of
  5  // interpreting the program counter (pc) values returned by runtime.Callers.
  6  //
  7  // Package stack's types implement fmt.Formatter, which provides a simple and
  8  // flexible way to declaratively configure formatting when used with logging
  9  // or error tracking packages.
 10  package stack
 11  
 12  import (
 13  	"bytes"
 14  	"errors"
 15  	"fmt"
 16  	"io"
 17  	"runtime"
 18  	"strconv"
 19  	"strings"
 20  )
 21  
 22  // Call records a single function invocation from a goroutine stack.
 23  type Call struct {
 24  	fn *runtime.Func
 25  	pc uintptr
 26  }
 27  
 28  // Caller returns a Call from the stack of the current goroutine. The argument
 29  // skip is the number of stack frames to ascend, with 0 identifying the
 30  // calling function.
 31  func Caller(skip int) Call {
 32  	var pcs [2]uintptr
 33  	n := runtime.Callers(skip+1, pcs[:])
 34  
 35  	var c Call
 36  
 37  	if n < 2 {
 38  		return c
 39  	}
 40  
 41  	c.pc = pcs[1]
 42  	if runtime.FuncForPC(pcs[0]).Name() != "runtime.sigpanic" {
 43  		c.pc--
 44  	}
 45  	c.fn = runtime.FuncForPC(c.pc)
 46  	return c
 47  }
 48  
 49  // String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c).
 50  func (c Call) String() string {
 51  	return fmt.Sprint(c)
 52  }
 53  
 54  // MarshalText implements encoding.TextMarshaler. It formats the Call the same
 55  // as fmt.Sprintf("%v", c).
 56  func (c Call) MarshalText() ([]byte, error) {
 57  	if c.fn == nil {
 58  		return nil, ErrNoFunc
 59  	}
 60  	buf := bytes.Buffer{}
 61  	fmt.Fprint(&buf, c)
 62  	return buf.Bytes(), nil
 63  }
 64  
 65  // ErrNoFunc means that the Call has a nil *runtime.Func. The most likely
 66  // cause is a Call with the zero value.
 67  var ErrNoFunc = errors.New("no call stack information")
 68  
 69  // Format implements fmt.Formatter with support for the following verbs.
 70  //
 71  //    %s    source file
 72  //    %d    line number
 73  //    %n    function name
 74  //    %v    equivalent to %s:%d
 75  //
 76  // It accepts the '+' and '#' flags for most of the verbs as follows.
 77  //
 78  //    %+s   path of source file relative to the compile time GOPATH
 79  //    %#s   full path of source file
 80  //    %+n   import path qualified function name
 81  //    %+v   equivalent to %+s:%d
 82  //    %#v   equivalent to %#s:%d
 83  func (c Call) Format(s fmt.State, verb rune) {
 84  	if c.fn == nil {
 85  		fmt.Fprintf(s, "%%!%c(NOFUNC)", verb)
 86  		return
 87  	}
 88  
 89  	switch verb {
 90  	case 's', 'v':
 91  		file, line := c.fn.FileLine(c.pc)
 92  		switch {
 93  		case s.Flag('#'):
 94  			// done
 95  		case s.Flag('+'):
 96  			file = file[pkgIndex(file, c.fn.Name()):]
 97  		default:
 98  			const sep = "/"
 99  			if i := strings.LastIndex(file, sep); i != -1 {
100  				file = file[i+len(sep):]
101  			}
102  		}
103  		io.WriteString(s, file)
104  		if verb == 'v' {
105  			buf := [7]byte{':'}
106  			s.Write(strconv.AppendInt(buf[:1], int64(line), 10))
107  		}
108  
109  	case 'd':
110  		_, line := c.fn.FileLine(c.pc)
111  		buf := [6]byte{}
112  		s.Write(strconv.AppendInt(buf[:0], int64(line), 10))
113  
114  	case 'n':
115  		name := c.fn.Name()
116  		if !s.Flag('+') {
117  			const pathSep = "/"
118  			if i := strings.LastIndex(name, pathSep); i != -1 {
119  				name = name[i+len(pathSep):]
120  			}
121  			const pkgSep = "."
122  			if i := strings.Index(name, pkgSep); i != -1 {
123  				name = name[i+len(pkgSep):]
124  			}
125  		}
126  		io.WriteString(s, name)
127  	}
128  }
129  
130  // PC returns the program counter for this call frame; multiple frames may
131  // have the same PC value.
132  func (c Call) PC() uintptr {
133  	return c.pc
134  }
135  
136  // name returns the import path qualified name of the function containing the
137  // call.
138  func (c Call) name() string {
139  	if c.fn == nil {
140  		return "???"
141  	}
142  	return c.fn.Name()
143  }
144  
145  func (c Call) file() string {
146  	if c.fn == nil {
147  		return "???"
148  	}
149  	file, _ := c.fn.FileLine(c.pc)
150  	return file
151  }
152  
153  func (c Call) line() int {
154  	if c.fn == nil {
155  		return 0
156  	}
157  	_, line := c.fn.FileLine(c.pc)
158  	return line
159  }
160  
161  // CallStack records a sequence of function invocations from a goroutine
162  // stack.
163  type CallStack []Call
164  
165  // String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", cs).
166  func (cs CallStack) String() string {
167  	return fmt.Sprint(cs)
168  }
169  
170  var (
171  	openBracketBytes  = []byte("[")
172  	closeBracketBytes = []byte("]")
173  	spaceBytes        = []byte(" ")
174  )
175  
176  // MarshalText implements encoding.TextMarshaler. It formats the CallStack the
177  // same as fmt.Sprintf("%v", cs).
178  func (cs CallStack) MarshalText() ([]byte, error) {
179  	buf := bytes.Buffer{}
180  	buf.Write(openBracketBytes)
181  	for i, pc := range cs {
182  		if pc.fn == nil {
183  			return nil, ErrNoFunc
184  		}
185  		if i > 0 {
186  			buf.Write(spaceBytes)
187  		}
188  		fmt.Fprint(&buf, pc)
189  	}
190  	buf.Write(closeBracketBytes)
191  	return buf.Bytes(), nil
192  }
193  
194  // Format implements fmt.Formatter by printing the CallStack as square brackets
195  // ([, ]) surrounding a space separated list of Calls each formatted with the
196  // supplied verb and options.
197  func (cs CallStack) Format(s fmt.State, verb rune) {
198  	s.Write(openBracketBytes)
199  	for i, pc := range cs {
200  		if i > 0 {
201  			s.Write(spaceBytes)
202  		}
203  		pc.Format(s, verb)
204  	}
205  	s.Write(closeBracketBytes)
206  }
207  
208  // Trace returns a CallStack for the current goroutine with element 0
209  // identifying the calling function.
210  func Trace() CallStack {
211  	var pcs [512]uintptr
212  	n := runtime.Callers(2, pcs[:])
213  	cs := make([]Call, n)
214  
215  	for i, pc := range pcs[:n] {
216  		pcFix := pc
217  		if i > 0 && cs[i-1].fn.Name() != "runtime.sigpanic" {
218  			pcFix--
219  		}
220  		cs[i] = Call{
221  			fn: runtime.FuncForPC(pcFix),
222  			pc: pcFix,
223  		}
224  	}
225  
226  	return cs
227  }
228  
229  // TrimBelow returns a slice of the CallStack with all entries below c
230  // removed.
231  func (cs CallStack) TrimBelow(c Call) CallStack {
232  	for len(cs) > 0 && cs[0].pc != c.pc {
233  		cs = cs[1:]
234  	}
235  	return cs
236  }
237  
238  // TrimAbove returns a slice of the CallStack with all entries above c
239  // removed.
240  func (cs CallStack) TrimAbove(c Call) CallStack {
241  	for len(cs) > 0 && cs[len(cs)-1].pc != c.pc {
242  		cs = cs[:len(cs)-1]
243  	}
244  	return cs
245  }
246  
247  // pkgIndex returns the index that results in file[index:] being the path of
248  // file relative to the compile time GOPATH, and file[:index] being the
249  // $GOPATH/src/ portion of file. funcName must be the name of a function in
250  // file as returned by runtime.Func.Name.
251  func pkgIndex(file, funcName string) int {
252  	// As of Go 1.6.2 there is no direct way to know the compile time GOPATH
253  	// at runtime, but we can infer the number of path segments in the GOPATH.
254  	// We note that runtime.Func.Name() returns the function name qualified by
255  	// the import path, which does not include the GOPATH. Thus we can trim
256  	// segments from the beginning of the file path until the number of path
257  	// separators remaining is one more than the number of path separators in
258  	// the function name. For example, given:
259  	//
260  	//    GOPATH     /home/user
261  	//    file       /home/user/src/pkg/sub/file.go
262  	//    fn.Name()  pkg/sub.Type.Method
263  	//
264  	// We want to produce:
265  	//
266  	//    file[:idx] == /home/user/src/
267  	//    file[idx:] == pkg/sub/file.go
268  	//
269  	// From this we can easily see that fn.Name() has one less path separator
270  	// than our desired result for file[idx:]. We count separators from the
271  	// end of the file path until it finds two more than in the function name
272  	// and then move one character forward to preserve the initial path
273  	// segment without a leading separator.
274  	const sep = "/"
275  	i := len(file)
276  	for n := strings.Count(funcName, sep) + 2; n > 0; n-- {
277  		i = strings.LastIndex(file[:i], sep)
278  		if i == -1 {
279  			i = -len(sep)
280  			break
281  		}
282  	}
283  	// get back to 0 or trim the leading separator
284  	return i + len(sep)
285  }
286  
287  var runtimePath string
288  
289  func init() {
290  	var pcs [1]uintptr
291  	runtime.Callers(0, pcs[:])
292  	fn := runtime.FuncForPC(pcs[0])
293  	file, _ := fn.FileLine(pcs[0])
294  
295  	idx := pkgIndex(file, fn.Name())
296  
297  	runtimePath = file[:idx]
298  	if runtime.GOOS == "windows" {
299  		runtimePath = strings.ToLower(runtimePath)
300  	}
301  }
302  
303  func inGoroot(c Call) bool {
304  	file := c.file()
305  	if len(file) == 0 || file[0] == '?' {
306  		return true
307  	}
308  	if runtime.GOOS == "windows" {
309  		file = strings.ToLower(file)
310  	}
311  	return strings.HasPrefix(file, runtimePath) || strings.HasSuffix(file, "/_testmain.go")
312  }
313  
314  // TrimRuntime returns a slice of the CallStack with the topmost entries from
315  // the go runtime removed. It considers any calls originating from unknown
316  // files, files under GOROOT, or _testmain.go as part of the runtime.
317  func (cs CallStack) TrimRuntime() CallStack {
318  	for len(cs) > 0 && inGoroot(cs[len(cs)-1]) {
319  		cs = cs[:len(cs)-1]
320  	}
321  	return cs
322  }