...

Source file src/github.com/go-stack/stack/stack.go

Documentation: github.com/go-stack/stack

     1  // +build go1.7
     2  
     3  // Package stack implements utilities to capture, manipulate, and format call
     4  // stacks. It provides a simpler API than package runtime.
     5  //
     6  // The implementation takes care of the minutia and special cases of
     7  // interpreting the program counter (pc) values returned by runtime.Callers.
     8  //
     9  // Package stack's types implement fmt.Formatter, which provides a simple and
    10  // flexible way to declaratively configure formatting when used with logging
    11  // or error tracking packages.
    12  package stack
    13  
    14  import (
    15  	"bytes"
    16  	"errors"
    17  	"fmt"
    18  	"io"
    19  	"runtime"
    20  	"strconv"
    21  	"strings"
    22  )
    23  
    24  // Call records a single function invocation from a goroutine stack.
    25  type Call struct {
    26  	frame runtime.Frame
    27  }
    28  
    29  // Caller returns a Call from the stack of the current goroutine. The argument
    30  // skip is the number of stack frames to ascend, with 0 identifying the
    31  // calling function.
    32  func Caller(skip int) Call {
    33  	// As of Go 1.9 we need room for up to three PC entries.
    34  	//
    35  	// 0. An entry for the stack frame prior to the target to check for
    36  	//    special handling needed if that prior entry is runtime.sigpanic.
    37  	// 1. A possible second entry to hold metadata about skipped inlined
    38  	//    functions. If inline functions were not skipped the target frame
    39  	//    PC will be here.
    40  	// 2. A third entry for the target frame PC when the second entry
    41  	//    is used for skipped inline functions.
    42  	var pcs [3]uintptr
    43  	n := runtime.Callers(skip+1, pcs[:])
    44  	frames := runtime.CallersFrames(pcs[:n])
    45  	frame, _ := frames.Next()
    46  	frame, _ = frames.Next()
    47  
    48  	return Call{
    49  		frame: frame,
    50  	}
    51  }
    52  
    53  // String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c).
    54  func (c Call) String() string {
    55  	return fmt.Sprint(c)
    56  }
    57  
    58  // MarshalText implements encoding.TextMarshaler. It formats the Call the same
    59  // as fmt.Sprintf("%v", c).
    60  func (c Call) MarshalText() ([]byte, error) {
    61  	if c.frame == (runtime.Frame{}) {
    62  		return nil, ErrNoFunc
    63  	}
    64  
    65  	buf := bytes.Buffer{}
    66  	fmt.Fprint(&buf, c)
    67  	return buf.Bytes(), nil
    68  }
    69  
    70  // ErrNoFunc means that the Call has a nil *runtime.Func. The most likely
    71  // cause is a Call with the zero value.
    72  var ErrNoFunc = errors.New("no call stack information")
    73  
    74  // Format implements fmt.Formatter with support for the following verbs.
    75  //
    76  //    %s    source file
    77  //    %d    line number
    78  //    %n    function name
    79  //    %k    last segment of the package path
    80  //    %v    equivalent to %s:%d
    81  //
    82  // It accepts the '+' and '#' flags for most of the verbs as follows.
    83  //
    84  //    %+s   path of source file relative to the compile time GOPATH,
    85  //          or the module path joined to the path of source file relative
    86  //          to module root
    87  //    %#s   full path of source file
    88  //    %+n   import path qualified function name
    89  //    %+k   full package path
    90  //    %+v   equivalent to %+s:%d
    91  //    %#v   equivalent to %#s:%d
    92  func (c Call) Format(s fmt.State, verb rune) {
    93  	if c.frame == (runtime.Frame{}) {
    94  		fmt.Fprintf(s, "%%!%c(NOFUNC)", verb)
    95  		return
    96  	}
    97  
    98  	switch verb {
    99  	case 's', 'v':
   100  		file := c.frame.File
   101  		switch {
   102  		case s.Flag('#'):
   103  			// done
   104  		case s.Flag('+'):
   105  			file = pkgFilePath(&c.frame)
   106  		default:
   107  			const sep = "/"
   108  			if i := strings.LastIndex(file, sep); i != -1 {
   109  				file = file[i+len(sep):]
   110  			}
   111  		}
   112  		io.WriteString(s, file)
   113  		if verb == 'v' {
   114  			buf := [7]byte{':'}
   115  			s.Write(strconv.AppendInt(buf[:1], int64(c.frame.Line), 10))
   116  		}
   117  
   118  	case 'd':
   119  		buf := [6]byte{}
   120  		s.Write(strconv.AppendInt(buf[:0], int64(c.frame.Line), 10))
   121  
   122  	case 'k':
   123  		name := c.frame.Function
   124  		const pathSep = "/"
   125  		start, end := 0, len(name)
   126  		if i := strings.LastIndex(name, pathSep); i != -1 {
   127  			start = i + len(pathSep)
   128  		}
   129  		const pkgSep = "."
   130  		if i := strings.Index(name[start:], pkgSep); i != -1 {
   131  			end = start + i
   132  		}
   133  		if s.Flag('+') {
   134  			start = 0
   135  		}
   136  		io.WriteString(s, name[start:end])
   137  
   138  	case 'n':
   139  		name := c.frame.Function
   140  		if !s.Flag('+') {
   141  			const pathSep = "/"
   142  			if i := strings.LastIndex(name, pathSep); i != -1 {
   143  				name = name[i+len(pathSep):]
   144  			}
   145  			const pkgSep = "."
   146  			if i := strings.Index(name, pkgSep); i != -1 {
   147  				name = name[i+len(pkgSep):]
   148  			}
   149  		}
   150  		io.WriteString(s, name)
   151  	}
   152  }
   153  
   154  // Frame returns the call frame infomation for the Call.
   155  func (c Call) Frame() runtime.Frame {
   156  	return c.frame
   157  }
   158  
   159  // PC returns the program counter for this call frame; multiple frames may
   160  // have the same PC value.
   161  //
   162  // Deprecated: Use Call.Frame instead.
   163  func (c Call) PC() uintptr {
   164  	return c.frame.PC
   165  }
   166  
   167  // CallStack records a sequence of function invocations from a goroutine
   168  // stack.
   169  type CallStack []Call
   170  
   171  // String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", cs).
   172  func (cs CallStack) String() string {
   173  	return fmt.Sprint(cs)
   174  }
   175  
   176  var (
   177  	openBracketBytes  = []byte("[")
   178  	closeBracketBytes = []byte("]")
   179  	spaceBytes        = []byte(" ")
   180  )
   181  
   182  // MarshalText implements encoding.TextMarshaler. It formats the CallStack the
   183  // same as fmt.Sprintf("%v", cs).
   184  func (cs CallStack) MarshalText() ([]byte, error) {
   185  	buf := bytes.Buffer{}
   186  	buf.Write(openBracketBytes)
   187  	for i, pc := range cs {
   188  		if i > 0 {
   189  			buf.Write(spaceBytes)
   190  		}
   191  		fmt.Fprint(&buf, pc)
   192  	}
   193  	buf.Write(closeBracketBytes)
   194  	return buf.Bytes(), nil
   195  }
   196  
   197  // Format implements fmt.Formatter by printing the CallStack as square brackets
   198  // ([, ]) surrounding a space separated list of Calls each formatted with the
   199  // supplied verb and options.
   200  func (cs CallStack) Format(s fmt.State, verb rune) {
   201  	s.Write(openBracketBytes)
   202  	for i, pc := range cs {
   203  		if i > 0 {
   204  			s.Write(spaceBytes)
   205  		}
   206  		pc.Format(s, verb)
   207  	}
   208  	s.Write(closeBracketBytes)
   209  }
   210  
   211  // Trace returns a CallStack for the current goroutine with element 0
   212  // identifying the calling function.
   213  func Trace() CallStack {
   214  	var pcs [512]uintptr
   215  	n := runtime.Callers(1, pcs[:])
   216  
   217  	frames := runtime.CallersFrames(pcs[:n])
   218  	cs := make(CallStack, 0, n)
   219  
   220  	// Skip extra frame retrieved just to make sure the runtime.sigpanic
   221  	// special case is handled.
   222  	frame, more := frames.Next()
   223  
   224  	for more {
   225  		frame, more = frames.Next()
   226  		cs = append(cs, Call{frame: frame})
   227  	}
   228  
   229  	return cs
   230  }
   231  
   232  // TrimBelow returns a slice of the CallStack with all entries below c
   233  // removed.
   234  func (cs CallStack) TrimBelow(c Call) CallStack {
   235  	for len(cs) > 0 && cs[0] != c {
   236  		cs = cs[1:]
   237  	}
   238  	return cs
   239  }
   240  
   241  // TrimAbove returns a slice of the CallStack with all entries above c
   242  // removed.
   243  func (cs CallStack) TrimAbove(c Call) CallStack {
   244  	for len(cs) > 0 && cs[len(cs)-1] != c {
   245  		cs = cs[:len(cs)-1]
   246  	}
   247  	return cs
   248  }
   249  
   250  // pkgIndex returns the index that results in file[index:] being the path of
   251  // file relative to the compile time GOPATH, and file[:index] being the
   252  // $GOPATH/src/ portion of file. funcName must be the name of a function in
   253  // file as returned by runtime.Func.Name.
   254  func pkgIndex(file, funcName string) int {
   255  	// As of Go 1.6.2 there is no direct way to know the compile time GOPATH
   256  	// at runtime, but we can infer the number of path segments in the GOPATH.
   257  	// We note that runtime.Func.Name() returns the function name qualified by
   258  	// the import path, which does not include the GOPATH. Thus we can trim
   259  	// segments from the beginning of the file path until the number of path
   260  	// separators remaining is one more than the number of path separators in
   261  	// the function name. For example, given:
   262  	//
   263  	//    GOPATH     /home/user
   264  	//    file       /home/user/src/pkg/sub/file.go
   265  	//    fn.Name()  pkg/sub.Type.Method
   266  	//
   267  	// We want to produce:
   268  	//
   269  	//    file[:idx] == /home/user/src/
   270  	//    file[idx:] == pkg/sub/file.go
   271  	//
   272  	// From this we can easily see that fn.Name() has one less path separator
   273  	// than our desired result for file[idx:]. We count separators from the
   274  	// end of the file path until it finds two more than in the function name
   275  	// and then move one character forward to preserve the initial path
   276  	// segment without a leading separator.
   277  	const sep = "/"
   278  	i := len(file)
   279  	for n := strings.Count(funcName, sep) + 2; n > 0; n-- {
   280  		i = strings.LastIndex(file[:i], sep)
   281  		if i == -1 {
   282  			i = -len(sep)
   283  			break
   284  		}
   285  	}
   286  	// get back to 0 or trim the leading separator
   287  	return i + len(sep)
   288  }
   289  
   290  // pkgFilePath returns the frame's filepath relative to the compile-time GOPATH,
   291  // or its module path joined to its path relative to the module root.
   292  //
   293  // As of Go 1.11 there is no direct way to know the compile time GOPATH or
   294  // module paths at runtime, but we can piece together the desired information
   295  // from available information. We note that runtime.Frame.Function contains the
   296  // function name qualified by the package path, which includes the module path
   297  // but not the GOPATH. We can extract the package path from that and append the
   298  // last segments of the file path to arrive at the desired package qualified
   299  // file path. For example, given:
   300  //
   301  //    GOPATH          /home/user
   302  //    import path     pkg/sub
   303  //    frame.File      /home/user/src/pkg/sub/file.go
   304  //    frame.Function  pkg/sub.Type.Method
   305  //    Desired return  pkg/sub/file.go
   306  //
   307  // It appears that we simply need to trim ".Type.Method" from frame.Function and
   308  // append "/" + path.Base(file).
   309  //
   310  // But there are other wrinkles. Although it is idiomatic to do so, the internal
   311  // name of a package is not required to match the last segment of its import
   312  // path. In addition, the introduction of modules in Go 1.11 allows working
   313  // without a GOPATH. So we also must make these work right:
   314  //
   315  //    GOPATH          /home/user
   316  //    import path     pkg/go-sub
   317  //    package name    sub
   318  //    frame.File      /home/user/src/pkg/go-sub/file.go
   319  //    frame.Function  pkg/sub.Type.Method
   320  //    Desired return  pkg/go-sub/file.go
   321  //
   322  //    Module path     pkg/v2
   323  //    import path     pkg/v2/go-sub
   324  //    package name    sub
   325  //    frame.File      /home/user/cloned-pkg/go-sub/file.go
   326  //    frame.Function  pkg/v2/sub.Type.Method
   327  //    Desired return  pkg/v2/go-sub/file.go
   328  //
   329  // We can handle all of these situations by using the package path extracted
   330  // from frame.Function up to, but not including, the last segment as the prefix
   331  // and the last two segments of frame.File as the suffix of the returned path.
   332  // This preserves the existing behavior when working in a GOPATH without modules
   333  // and a semantically equivalent behavior when used in module aware project.
   334  func pkgFilePath(frame *runtime.Frame) string {
   335  	pre := pkgPrefix(frame.Function)
   336  	post := pathSuffix(frame.File)
   337  	if pre == "" {
   338  		return post
   339  	}
   340  	return pre + "/" + post
   341  }
   342  
   343  // pkgPrefix returns the import path of the function's package with the final
   344  // segment removed.
   345  func pkgPrefix(funcName string) string {
   346  	const pathSep = "/"
   347  	end := strings.LastIndex(funcName, pathSep)
   348  	if end == -1 {
   349  		return ""
   350  	}
   351  	return funcName[:end]
   352  }
   353  
   354  // pathSuffix returns the last two segments of path.
   355  func pathSuffix(path string) string {
   356  	const pathSep = "/"
   357  	lastSep := strings.LastIndex(path, pathSep)
   358  	if lastSep == -1 {
   359  		return path
   360  	}
   361  	return path[strings.LastIndex(path[:lastSep], pathSep)+1:]
   362  }
   363  
   364  var runtimePath string
   365  
   366  func init() {
   367  	var pcs [3]uintptr
   368  	runtime.Callers(0, pcs[:])
   369  	frames := runtime.CallersFrames(pcs[:])
   370  	frame, _ := frames.Next()
   371  	file := frame.File
   372  
   373  	idx := pkgIndex(frame.File, frame.Function)
   374  
   375  	runtimePath = file[:idx]
   376  	if runtime.GOOS == "windows" {
   377  		runtimePath = strings.ToLower(runtimePath)
   378  	}
   379  }
   380  
   381  func inGoroot(c Call) bool {
   382  	file := c.frame.File
   383  	if len(file) == 0 || file[0] == '?' {
   384  		return true
   385  	}
   386  	if runtime.GOOS == "windows" {
   387  		file = strings.ToLower(file)
   388  	}
   389  	return strings.HasPrefix(file, runtimePath) || strings.HasSuffix(file, "/_testmain.go")
   390  }
   391  
   392  // TrimRuntime returns a slice of the CallStack with the topmost entries from
   393  // the go runtime removed. It considers any calls originating from unknown
   394  // files, files under GOROOT, or _testmain.go as part of the runtime.
   395  func (cs CallStack) TrimRuntime() CallStack {
   396  	for len(cs) > 0 && inGoroot(cs[len(cs)-1]) {
   397  		cs = cs[:len(cs)-1]
   398  	}
   399  	return cs
   400  }
   401  

View as plain text