go-tetris/vendor/gopkg.in/inconshreveable/log15.v2/stack/stack.go

226 lines
5.9 KiB
Go

// Package stack implements utilities to capture, manipulate, and format call
// stacks.
package stack
import (
"fmt"
"path/filepath"
"runtime"
"strings"
)
// Call records a single function invocation from a goroutine stack. It is a
// wrapper for the program counter values returned by runtime.Caller and
// runtime.Callers and consumed by runtime.FuncForPC.
type Call uintptr
// 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 (pc Call) Format(s fmt.State, c rune) {
// BUG(ChrisHines): Subtracting one from pc is a work around for
// https://code.google.com/p/go/issues/detail?id=7690. The idea for this
// work around comes from rsc's initial patch at
// https://codereview.appspot.com/84100043/#ps20001, but as noted in the
// issue discussion, it is not a complete fix since it doesn't handle some
// cases involving signals. Just the same, it handles all of the other
// cases I have tested.
pcFix := uintptr(pc) - 1
fn := runtime.FuncForPC(pcFix)
if fn == nil {
fmt.Fprintf(s, "%%!%c(NOFUNC)", c)
return
}
switch c {
case 's', 'v':
file, line := fn.FileLine(pcFix)
switch {
case s.Flag('#'):
// done
case s.Flag('+'):
// Here we want to get the source file path relative to the
// compile time GOPATH. As of Go 1.3.x there is no direct way to
// know the compiled GOPATH at runtime, but we can infer the
// number of path segments in the GOPATH. We note that fn.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:
//
// pkg/sub/file.go
//
// From this we can easily see that fn.Name() has one less path
// separator than our desired output.
const sep = "/"
impCnt := strings.Count(fn.Name(), sep) + 1
pathCnt := strings.Count(file, sep)
for pathCnt > impCnt {
i := strings.Index(file, sep)
if i == -1 {
break
}
file = file[i+len(sep):]
pathCnt--
}
default:
const sep = "/"
if i := strings.LastIndex(file, sep); i != -1 {
file = file[i+len(sep):]
}
}
fmt.Fprint(s, file)
if c == 'v' {
fmt.Fprint(s, ":", line)
}
case 'd':
_, line := fn.FileLine(pcFix)
fmt.Fprint(s, line)
case 'n':
name := 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):]
}
}
fmt.Fprint(s, name)
}
}
// Callers returns a Trace for the current goroutine with element 0
// identifying the calling function.
func Callers() Trace {
pcs := poolBuf()
pcs = pcs[:cap(pcs)]
n := runtime.Callers(2, pcs)
cs := make([]Call, n)
for i, pc := range pcs[:n] {
cs[i] = Call(pc)
}
putPoolBuf(pcs)
return cs
}
// name returns the import path qualified name of the function containing the
// call.
func (pc Call) name() string {
pcFix := uintptr(pc) - 1 // work around for go issue #7690
fn := runtime.FuncForPC(pcFix)
if fn == nil {
return "???"
}
return fn.Name()
}
func (pc Call) file() string {
pcFix := uintptr(pc) - 1 // work around for go issue #7690
fn := runtime.FuncForPC(pcFix)
if fn == nil {
return "???"
}
file, _ := fn.FileLine(pcFix)
return file
}
// Trace records a sequence of function invocations from a goroutine stack.
type Trace []Call
// Format implements fmt.Formatter by printing the Trace as square brackes ([,
// ]) surrounding a space separated list of Calls each formatted with the
// supplied verb and options.
func (pcs Trace) Format(s fmt.State, c rune) {
s.Write([]byte("["))
for i, pc := range pcs {
if i > 0 {
s.Write([]byte(" "))
}
pc.Format(s, c)
}
s.Write([]byte("]"))
}
// TrimBelow returns a slice of the Trace with all entries below pc removed.
func (pcs Trace) TrimBelow(pc Call) Trace {
for len(pcs) > 0 && pcs[0] != pc {
pcs = pcs[1:]
}
return pcs
}
// TrimAbove returns a slice of the Trace with all entries above pc removed.
func (pcs Trace) TrimAbove(pc Call) Trace {
for len(pcs) > 0 && pcs[len(pcs)-1] != pc {
pcs = pcs[:len(pcs)-1]
}
return pcs
}
// TrimBelowName returns a slice of the Trace with all entries below the
// lowest with function name name removed.
func (pcs Trace) TrimBelowName(name string) Trace {
for len(pcs) > 0 && pcs[0].name() != name {
pcs = pcs[1:]
}
return pcs
}
// TrimAboveName returns a slice of the Trace with all entries above the
// highest with function name name removed.
func (pcs Trace) TrimAboveName(name string) Trace {
for len(pcs) > 0 && pcs[len(pcs)-1].name() != name {
pcs = pcs[:len(pcs)-1]
}
return pcs
}
var goroot string
func init() {
goroot = filepath.ToSlash(runtime.GOROOT())
if runtime.GOOS == "windows" {
goroot = strings.ToLower(goroot)
}
}
func inGoroot(path string) bool {
if runtime.GOOS == "windows" {
path = strings.ToLower(path)
}
return strings.HasPrefix(path, goroot)
}
// TrimRuntime returns a slice of the Trace with the topmost entries from the
// go runtime removed. It considers any calls originating from files under
// GOROOT as part of the runtime.
func (pcs Trace) TrimRuntime() Trace {
for len(pcs) > 0 && inGoroot(pcs[len(pcs)-1].file()) {
pcs = pcs[:len(pcs)-1]
}
return pcs
}