| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- // Package stack implements utilities to capture, manipulate, and format call
- // stacks. It provides a simpler API than package runtime.
- //
- // The implementation takes care of the minutia and special cases of
- // interpreting the program counter (pc) values returned by runtime.Callers.
- //
- // Package stack's types implement fmt.Formatter, which provides a simple and
- // flexible way to declaratively configure formatting when used with logging
- // or error tracking packages.
- package stack
- import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "runtime"
- "strconv"
- "strings"
- )
- // Call records a single function invocation from a goroutine stack.
- type Call struct {
- fn *runtime.Func
- pc uintptr
- }
- // Caller returns a Call from the stack of the current goroutine. The argument
- // skip is the number of stack frames to ascend, with 0 identifying the
- // calling function.
- func Caller(skip int) Call {
- var pcs [2]uintptr
- n := runtime.Callers(skip+1, pcs[:])
- var c Call
- if n < 2 {
- return c
- }
- c.pc = pcs[1]
- if runtime.FuncForPC(pcs[0]) != sigpanic {
- c.pc--
- }
- c.fn = runtime.FuncForPC(c.pc)
- return c
- }
- // String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c).
- func (c Call) String() string {
- return fmt.Sprint(c)
- }
- // MarshalText implements encoding.TextMarshaler. It formats the Call the same
- // as fmt.Sprintf("%v", c).
- func (c Call) MarshalText() ([]byte, error) {
- if c.fn == nil {
- return nil, ErrNoFunc
- }
- buf := bytes.Buffer{}
- fmt.Fprint(&buf, c)
- return buf.Bytes(), nil
- }
- // ErrNoFunc means that the Call has a nil *runtime.Func. The most likely
- // cause is a Call with the zero value.
- var ErrNoFunc = errors.New("no call stack information")
- // Format implements fmt.Formatter with support for the following verbs.
- //
- // %s source file
- // %d line number
- // %n function name
- // %v equivalent to %s:%d
- //
- // It accepts the '+' and '#' flags for most of the verbs as follows.
- //
- // %+s path of source file relative to the compile time GOPATH
- // %#s full path of source file
- // %+n import path qualified function name
- // %+v equivalent to %+s:%d
- // %#v equivalent to %#s:%d
- func (c Call) Format(s fmt.State, verb rune) {
- if c.fn == nil {
- fmt.Fprintf(s, "%%!%c(NOFUNC)", verb)
- return
- }
- switch verb {
- case 's', 'v':
- file, line := c.fn.FileLine(c.pc)
- switch {
- case s.Flag('#'):
- // done
- case s.Flag('+'):
- file = file[pkgIndex(file, c.fn.Name()):]
- default:
- const sep = "/"
- if i := strings.LastIndex(file, sep); i != -1 {
- file = file[i+len(sep):]
- }
- }
- io.WriteString(s, file)
- if verb == 'v' {
- buf := [7]byte{':'}
- s.Write(strconv.AppendInt(buf[:1], int64(line), 10))
- }
- case 'd':
- _, line := c.fn.FileLine(c.pc)
- buf := [6]byte{}
- s.Write(strconv.AppendInt(buf[:0], int64(line), 10))
- case 'n':
- name := c.fn.Name()
- if !s.Flag('+') {
- const pathSep = "/"
- if i := strings.LastIndex(name, pathSep); i != -1 {
- name = name[i+len(pathSep):]
- }
- const pkgSep = "."
- if i := strings.Index(name, pkgSep); i != -1 {
- name = name[i+len(pkgSep):]
- }
- }
- io.WriteString(s, name)
- }
- }
- // PC returns the program counter for this call frame; multiple frames may
- // have the same PC value.
- func (c Call) PC() uintptr {
- return c.pc
- }
- // name returns the import path qualified name of the function containing the
- // call.
- func (c Call) name() string {
- if c.fn == nil {
- return "???"
- }
- return c.fn.Name()
- }
- func (c Call) file() string {
- if c.fn == nil {
- return "???"
- }
- file, _ := c.fn.FileLine(c.pc)
- return file
- }
- func (c Call) line() int {
- if c.fn == nil {
- return 0
- }
- _, line := c.fn.FileLine(c.pc)
- return line
- }
- // CallStack records a sequence of function invocations from a goroutine
- // stack.
- type CallStack []Call
- // String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", cs).
- func (cs CallStack) String() string {
- return fmt.Sprint(cs)
- }
- var (
- openBracketBytes = []byte("[")
- closeBracketBytes = []byte("]")
- spaceBytes = []byte(" ")
- )
- // MarshalText implements encoding.TextMarshaler. It formats the CallStack the
- // same as fmt.Sprintf("%v", cs).
- func (cs CallStack) MarshalText() ([]byte, error) {
- buf := bytes.Buffer{}
- buf.Write(openBracketBytes)
- for i, pc := range cs {
- if pc.fn == nil {
- return nil, ErrNoFunc
- }
- if i > 0 {
- buf.Write(spaceBytes)
- }
- fmt.Fprint(&buf, pc)
- }
- buf.Write(closeBracketBytes)
- return buf.Bytes(), nil
- }
- // Format implements fmt.Formatter by printing the CallStack as square brackets
- // ([, ]) surrounding a space separated list of Calls each formatted with the
- // supplied verb and options.
- func (cs CallStack) Format(s fmt.State, verb rune) {
- s.Write(openBracketBytes)
- for i, pc := range cs {
- if i > 0 {
- s.Write(spaceBytes)
- }
- pc.Format(s, verb)
- }
- s.Write(closeBracketBytes)
- }
- // findSigpanic intentionally executes faulting code to generate a stack trace
- // containing an entry for runtime.sigpanic.
- func findSigpanic() *runtime.Func {
- var fn *runtime.Func
- var p *int
- func() int {
- defer func() {
- if p := recover(); p != nil {
- var pcs [512]uintptr
- n := runtime.Callers(2, pcs[:])
- for _, pc := range pcs[:n] {
- f := runtime.FuncForPC(pc)
- if f.Name() == "runtime.sigpanic" {
- fn = f
- break
- }
- }
- }
- }()
- // intentional nil pointer dereference to trigger sigpanic
- return *p
- }()
- return fn
- }
- var sigpanic = findSigpanic()
- // Trace returns a CallStack for the current goroutine with element 0
- // identifying the calling function.
- func Trace() CallStack {
- var pcs [512]uintptr
- n := runtime.Callers(2, pcs[:])
- cs := make([]Call, n)
- for i, pc := range pcs[:n] {
- pcFix := pc
- if i > 0 && cs[i-1].fn != sigpanic {
- pcFix--
- }
- cs[i] = Call{
- fn: runtime.FuncForPC(pcFix),
- pc: pcFix,
- }
- }
- return cs
- }
- // TrimBelow returns a slice of the CallStack with all entries below c
- // removed.
- func (cs CallStack) TrimBelow(c Call) CallStack {
- for len(cs) > 0 && cs[0].pc != c.pc {
- cs = cs[1:]
- }
- return cs
- }
- // TrimAbove returns a slice of the CallStack with all entries above c
- // removed.
- func (cs CallStack) TrimAbove(c Call) CallStack {
- for len(cs) > 0 && cs[len(cs)-1].pc != c.pc {
- cs = cs[:len(cs)-1]
- }
- return cs
- }
- // pkgIndex returns the index that results in file[index:] being the path of
- // file relative to the compile time GOPATH, and file[:index] being the
- // $GOPATH/src/ portion of file. funcName must be the name of a function in
- // file as returned by runtime.Func.Name.
- func pkgIndex(file, funcName string) int {
- // As of Go 1.6.2 there is no direct way to know the compile time GOPATH
- // at runtime, but we can infer the number of path segments in the GOPATH.
- // We note that runtime.Func.Name() returns the function name qualified by
- // the import path, which does not include the GOPATH. Thus we can trim
- // segments from the beginning of the file path until the number of path
- // separators remaining is one more than the number of path separators in
- // the function name. For example, given:
- //
- // GOPATH /home/user
- // file /home/user/src/pkg/sub/file.go
- // fn.Name() pkg/sub.Type.Method
- //
- // We want to produce:
- //
- // file[:idx] == /home/user/src/
- // file[idx:] == pkg/sub/file.go
- //
- // From this we can easily see that fn.Name() has one less path separator
- // than our desired result for file[idx:]. We count separators from the
- // end of the file path until it finds two more than in the function name
- // and then move one character forward to preserve the initial path
- // segment without a leading separator.
- const sep = "/"
- i := len(file)
- for n := strings.Count(funcName, sep) + 2; n > 0; n-- {
- i = strings.LastIndex(file[:i], sep)
- if i == -1 {
- i = -len(sep)
- break
- }
- }
- // get back to 0 or trim the leading separator
- return i + len(sep)
- }
- var runtimePath string
- func init() {
- var pcs [1]uintptr
- runtime.Callers(0, pcs[:])
- fn := runtime.FuncForPC(pcs[0])
- file, _ := fn.FileLine(pcs[0])
- idx := pkgIndex(file, fn.Name())
- runtimePath = file[:idx]
- if runtime.GOOS == "windows" {
- runtimePath = strings.ToLower(runtimePath)
- }
- }
- func inGoroot(c Call) bool {
- file := c.file()
- if len(file) == 0 || file[0] == '?' {
- return true
- }
- if runtime.GOOS == "windows" {
- file = strings.ToLower(file)
- }
- return strings.HasPrefix(file, runtimePath) || strings.HasSuffix(file, "/_testmain.go")
- }
- // TrimRuntime returns a slice of the CallStack with the topmost entries from
- // the go runtime removed. It considers any calls originating from unknown
- // files, files under GOROOT, or _testmain.go as part of the runtime.
- func (cs CallStack) TrimRuntime() CallStack {
- for len(cs) > 0 && inGoroot(cs[len(cs)-1]) {
- cs = cs[:len(cs)-1]
- }
- return cs
- }
|