| // 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]).Name() != "runtime.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 |
| // %k last segment of the package path |
| // %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 |
| // %+k full package path |
| // %+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 'k': |
| name := c.fn.Name() |
| const pathSep = "/" |
| start, end := 0, len(name) |
| if i := strings.LastIndex(name, pathSep); i != -1 { |
| start = i + len(pathSep) |
| } |
| const pkgSep = "." |
| if i := strings.Index(name[start:], pkgSep); i != -1 { |
| end = start + i |
| } |
| if s.Flag('+') { |
| start = 0 |
| } |
| io.WriteString(s, name[start:end]) |
| |
| 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) |
| } |
| |
| // 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.Name() != "runtime.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 |
| } |