Don Newton | 379ae25 | 2019-04-01 12:17:06 -0400 | [diff] [blame] | 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 | } |