1 // Package stack implements utilities to capture, manipulate, and format call
2 // stacks. It provides a simpler API than package runtime.
4 // The implementation takes care of the minutia and special cases of
5 // interpreting the program counter (pc) values returned by runtime.Callers.
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.
22 // Call records a single function invocation from a goroutine stack.
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
31 func Caller(skip int) Call {
33 n := runtime.Callers(skip+1, pcs[:])
42 if runtime.FuncForPC(pcs[0]).Name() != "runtime.sigpanic" {
45 c.fn = runtime.FuncForPC(c.pc)
49 // String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c).
50 func (c Call) String() string {
54 // MarshalText implements encoding.TextMarshaler. It formats the Call the same
55 // as fmt.Sprintf("%v", c).
56 func (c Call) MarshalText() ([]byte, error) {
62 return buf.Bytes(), nil
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")
69 // Format implements fmt.Formatter with support for the following verbs.
74 // %k last segment of the package path
75 // %v equivalent to %s:%d
77 // It accepts the '+' and '#' flags for most of the verbs as follows.
79 // %+s path of source file relative to the compile time GOPATH
80 // %#s full path of source file
81 // %+n import path qualified function name
82 // %+k full package path
83 // %+v equivalent to %+s:%d
84 // %#v equivalent to %#s:%d
85 func (c Call) Format(s fmt.State, verb rune) {
87 fmt.Fprintf(s, "%%!%c(NOFUNC)", verb)
93 file, line := c.fn.FileLine(c.pc)
98 file = file[pkgIndex(file, c.fn.Name()):]
101 if i := strings.LastIndex(file, sep); i != -1 {
102 file = file[i+len(sep):]
105 io.WriteString(s, file)
108 s.Write(strconv.AppendInt(buf[:1], int64(line), 10))
112 _, line := c.fn.FileLine(c.pc)
114 s.Write(strconv.AppendInt(buf[:0], int64(line), 10))
119 start, end := 0, len(name)
120 if i := strings.LastIndex(name, pathSep); i != -1 {
121 start = i + len(pathSep)
124 if i := strings.Index(name[start:], pkgSep); i != -1 {
130 io.WriteString(s, name[start:end])
136 if i := strings.LastIndex(name, pathSep); i != -1 {
137 name = name[i+len(pathSep):]
140 if i := strings.Index(name, pkgSep); i != -1 {
141 name = name[i+len(pkgSep):]
144 io.WriteString(s, name)
148 // PC returns the program counter for this call frame; multiple frames may
149 // have the same PC value.
150 func (c Call) PC() uintptr {
154 // name returns the import path qualified name of the function containing the
156 func (c Call) name() string {
163 func (c Call) file() string {
167 file, _ := c.fn.FileLine(c.pc)
171 func (c Call) line() int {
175 _, line := c.fn.FileLine(c.pc)
179 // CallStack records a sequence of function invocations from a goroutine
181 type CallStack []Call
183 // String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", cs).
184 func (cs CallStack) String() string {
185 return fmt.Sprint(cs)
189 openBracketBytes = []byte("[")
190 closeBracketBytes = []byte("]")
191 spaceBytes = []byte(" ")
194 // MarshalText implements encoding.TextMarshaler. It formats the CallStack the
195 // same as fmt.Sprintf("%v", cs).
196 func (cs CallStack) MarshalText() ([]byte, error) {
197 buf := bytes.Buffer{}
198 buf.Write(openBracketBytes)
199 for i, pc := range cs {
201 return nil, ErrNoFunc
204 buf.Write(spaceBytes)
208 buf.Write(closeBracketBytes)
209 return buf.Bytes(), nil
212 // Format implements fmt.Formatter by printing the CallStack as square brackets
213 // ([, ]) surrounding a space separated list of Calls each formatted with the
214 // supplied verb and options.
215 func (cs CallStack) Format(s fmt.State, verb rune) {
216 s.Write(openBracketBytes)
217 for i, pc := range cs {
223 s.Write(closeBracketBytes)
226 // Trace returns a CallStack for the current goroutine with element 0
227 // identifying the calling function.
228 func Trace() CallStack {
230 n := runtime.Callers(2, pcs[:])
231 cs := make([]Call, n)
233 for i, pc := range pcs[:n] {
235 if i > 0 && cs[i-1].fn.Name() != "runtime.sigpanic" {
239 fn: runtime.FuncForPC(pcFix),
247 // TrimBelow returns a slice of the CallStack with all entries below c
249 func (cs CallStack) TrimBelow(c Call) CallStack {
250 for len(cs) > 0 && cs[0].pc != c.pc {
256 // TrimAbove returns a slice of the CallStack with all entries above c
258 func (cs CallStack) TrimAbove(c Call) CallStack {
259 for len(cs) > 0 && cs[len(cs)-1].pc != c.pc {
265 // pkgIndex returns the index that results in file[index:] being the path of
266 // file relative to the compile time GOPATH, and file[:index] being the
267 // $GOPATH/src/ portion of file. funcName must be the name of a function in
268 // file as returned by runtime.Func.Name.
269 func pkgIndex(file, funcName string) int {
270 // As of Go 1.6.2 there is no direct way to know the compile time GOPATH
271 // at runtime, but we can infer the number of path segments in the GOPATH.
272 // We note that runtime.Func.Name() returns the function name qualified by
273 // the import path, which does not include the GOPATH. Thus we can trim
274 // segments from the beginning of the file path until the number of path
275 // separators remaining is one more than the number of path separators in
276 // the function name. For example, given:
279 // file /home/user/src/pkg/sub/file.go
280 // fn.Name() pkg/sub.Type.Method
282 // We want to produce:
284 // file[:idx] == /home/user/src/
285 // file[idx:] == pkg/sub/file.go
287 // From this we can easily see that fn.Name() has one less path separator
288 // than our desired result for file[idx:]. We count separators from the
289 // end of the file path until it finds two more than in the function name
290 // and then move one character forward to preserve the initial path
291 // segment without a leading separator.
294 for n := strings.Count(funcName, sep) + 2; n > 0; n-- {
295 i = strings.LastIndex(file[:i], sep)
301 // get back to 0 or trim the leading separator
305 var runtimePath string
309 runtime.Callers(0, pcs[:])
310 fn := runtime.FuncForPC(pcs[0])
311 file, _ := fn.FileLine(pcs[0])
313 idx := pkgIndex(file, fn.Name())
315 runtimePath = file[:idx]
316 if runtime.GOOS == "windows" {
317 runtimePath = strings.ToLower(runtimePath)
321 func inGoroot(c Call) bool {
323 if len(file) == 0 || file[0] == '?' {
326 if runtime.GOOS == "windows" {
327 file = strings.ToLower(file)
329 return strings.HasPrefix(file, runtimePath) || strings.HasSuffix(file, "/_testmain.go")
332 // TrimRuntime returns a slice of the CallStack with the topmost entries from
333 // the go runtime removed. It considers any calls originating from unknown
334 // files, files under GOROOT, or _testmain.go as part of the runtime.
335 func (cs CallStack) TrimRuntime() CallStack {
336 for len(cs) > 0 && inGoroot(cs[len(cs)-1]) {