stack.go
1 // Package stack implements utilities to capture, manipulate, and format call 2 // stacks. It provides a simpler API than package runtime. 3 // 4 // The implementation takes care of the minutia and special cases of 5 // interpreting the program counter (pc) values returned by runtime.Callers. 6 // 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. 10 package stack 11 12 import ( 13 "bytes" 14 "errors" 15 "fmt" 16 "io" 17 "runtime" 18 "strconv" 19 "strings" 20 ) 21 22 // Call records a single function invocation from a goroutine stack. 23 type Call struct { 24 fn *runtime.Func 25 pc uintptr 26 } 27 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 30 // calling function. 31 func Caller(skip int) Call { 32 var pcs [2]uintptr 33 n := runtime.Callers(skip+1, pcs[:]) 34 35 var c Call 36 37 if n < 2 { 38 return c 39 } 40 41 c.pc = pcs[1] 42 if runtime.FuncForPC(pcs[0]).Name() != "runtime.sigpanic" { 43 c.pc-- 44 } 45 c.fn = runtime.FuncForPC(c.pc) 46 return c 47 } 48 49 // String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c). 50 func (c Call) String() string { 51 return fmt.Sprint(c) 52 } 53 54 // MarshalText implements encoding.TextMarshaler. It formats the Call the same 55 // as fmt.Sprintf("%v", c). 56 func (c Call) MarshalText() ([]byte, error) { 57 if c.fn == nil { 58 return nil, ErrNoFunc 59 } 60 buf := bytes.Buffer{} 61 fmt.Fprint(&buf, c) 62 return buf.Bytes(), nil 63 } 64 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") 68 69 // Format implements fmt.Formatter with support for the following verbs. 70 // 71 // %s source file 72 // %d line number 73 // %n function name 74 // %v equivalent to %s:%d 75 // 76 // It accepts the '+' and '#' flags for most of the verbs as follows. 77 // 78 // %+s path of source file relative to the compile time GOPATH 79 // %#s full path of source file 80 // %+n import path qualified function name 81 // %+v equivalent to %+s:%d 82 // %#v equivalent to %#s:%d 83 func (c Call) Format(s fmt.State, verb rune) { 84 if c.fn == nil { 85 fmt.Fprintf(s, "%%!%c(NOFUNC)", verb) 86 return 87 } 88 89 switch verb { 90 case 's', 'v': 91 file, line := c.fn.FileLine(c.pc) 92 switch { 93 case s.Flag('#'): 94 // done 95 case s.Flag('+'): 96 file = file[pkgIndex(file, c.fn.Name()):] 97 default: 98 const sep = "/" 99 if i := strings.LastIndex(file, sep); i != -1 { 100 file = file[i+len(sep):] 101 } 102 } 103 io.WriteString(s, file) 104 if verb == 'v' { 105 buf := [7]byte{':'} 106 s.Write(strconv.AppendInt(buf[:1], int64(line), 10)) 107 } 108 109 case 'd': 110 _, line := c.fn.FileLine(c.pc) 111 buf := [6]byte{} 112 s.Write(strconv.AppendInt(buf[:0], int64(line), 10)) 113 114 case 'n': 115 name := c.fn.Name() 116 if !s.Flag('+') { 117 const pathSep = "/" 118 if i := strings.LastIndex(name, pathSep); i != -1 { 119 name = name[i+len(pathSep):] 120 } 121 const pkgSep = "." 122 if i := strings.Index(name, pkgSep); i != -1 { 123 name = name[i+len(pkgSep):] 124 } 125 } 126 io.WriteString(s, name) 127 } 128 } 129 130 // PC returns the program counter for this call frame; multiple frames may 131 // have the same PC value. 132 func (c Call) PC() uintptr { 133 return c.pc 134 } 135 136 // name returns the import path qualified name of the function containing the 137 // call. 138 func (c Call) name() string { 139 if c.fn == nil { 140 return "???" 141 } 142 return c.fn.Name() 143 } 144 145 func (c Call) file() string { 146 if c.fn == nil { 147 return "???" 148 } 149 file, _ := c.fn.FileLine(c.pc) 150 return file 151 } 152 153 func (c Call) line() int { 154 if c.fn == nil { 155 return 0 156 } 157 _, line := c.fn.FileLine(c.pc) 158 return line 159 } 160 161 // CallStack records a sequence of function invocations from a goroutine 162 // stack. 163 type CallStack []Call 164 165 // String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", cs). 166 func (cs CallStack) String() string { 167 return fmt.Sprint(cs) 168 } 169 170 var ( 171 openBracketBytes = []byte("[") 172 closeBracketBytes = []byte("]") 173 spaceBytes = []byte(" ") 174 ) 175 176 // MarshalText implements encoding.TextMarshaler. It formats the CallStack the 177 // same as fmt.Sprintf("%v", cs). 178 func (cs CallStack) MarshalText() ([]byte, error) { 179 buf := bytes.Buffer{} 180 buf.Write(openBracketBytes) 181 for i, pc := range cs { 182 if pc.fn == nil { 183 return nil, ErrNoFunc 184 } 185 if i > 0 { 186 buf.Write(spaceBytes) 187 } 188 fmt.Fprint(&buf, pc) 189 } 190 buf.Write(closeBracketBytes) 191 return buf.Bytes(), nil 192 } 193 194 // Format implements fmt.Formatter by printing the CallStack as square brackets 195 // ([, ]) surrounding a space separated list of Calls each formatted with the 196 // supplied verb and options. 197 func (cs CallStack) Format(s fmt.State, verb rune) { 198 s.Write(openBracketBytes) 199 for i, pc := range cs { 200 if i > 0 { 201 s.Write(spaceBytes) 202 } 203 pc.Format(s, verb) 204 } 205 s.Write(closeBracketBytes) 206 } 207 208 // Trace returns a CallStack for the current goroutine with element 0 209 // identifying the calling function. 210 func Trace() CallStack { 211 var pcs [512]uintptr 212 n := runtime.Callers(2, pcs[:]) 213 cs := make([]Call, n) 214 215 for i, pc := range pcs[:n] { 216 pcFix := pc 217 if i > 0 && cs[i-1].fn.Name() != "runtime.sigpanic" { 218 pcFix-- 219 } 220 cs[i] = Call{ 221 fn: runtime.FuncForPC(pcFix), 222 pc: pcFix, 223 } 224 } 225 226 return cs 227 } 228 229 // TrimBelow returns a slice of the CallStack with all entries below c 230 // removed. 231 func (cs CallStack) TrimBelow(c Call) CallStack { 232 for len(cs) > 0 && cs[0].pc != c.pc { 233 cs = cs[1:] 234 } 235 return cs 236 } 237 238 // TrimAbove returns a slice of the CallStack with all entries above c 239 // removed. 240 func (cs CallStack) TrimAbove(c Call) CallStack { 241 for len(cs) > 0 && cs[len(cs)-1].pc != c.pc { 242 cs = cs[:len(cs)-1] 243 } 244 return cs 245 } 246 247 // pkgIndex returns the index that results in file[index:] being the path of 248 // file relative to the compile time GOPATH, and file[:index] being the 249 // $GOPATH/src/ portion of file. funcName must be the name of a function in 250 // file as returned by runtime.Func.Name. 251 func pkgIndex(file, funcName string) int { 252 // As of Go 1.6.2 there is no direct way to know the compile time GOPATH 253 // at runtime, but we can infer the number of path segments in the GOPATH. 254 // We note that runtime.Func.Name() returns the function name qualified by 255 // the import path, which does not include the GOPATH. Thus we can trim 256 // segments from the beginning of the file path until the number of path 257 // separators remaining is one more than the number of path separators in 258 // the function name. For example, given: 259 // 260 // GOPATH /home/user 261 // file /home/user/src/pkg/sub/file.go 262 // fn.Name() pkg/sub.Type.Method 263 // 264 // We want to produce: 265 // 266 // file[:idx] == /home/user/src/ 267 // file[idx:] == pkg/sub/file.go 268 // 269 // From this we can easily see that fn.Name() has one less path separator 270 // than our desired result for file[idx:]. We count separators from the 271 // end of the file path until it finds two more than in the function name 272 // and then move one character forward to preserve the initial path 273 // segment without a leading separator. 274 const sep = "/" 275 i := len(file) 276 for n := strings.Count(funcName, sep) + 2; n > 0; n-- { 277 i = strings.LastIndex(file[:i], sep) 278 if i == -1 { 279 i = -len(sep) 280 break 281 } 282 } 283 // get back to 0 or trim the leading separator 284 return i + len(sep) 285 } 286 287 var runtimePath string 288 289 func init() { 290 var pcs [1]uintptr 291 runtime.Callers(0, pcs[:]) 292 fn := runtime.FuncForPC(pcs[0]) 293 file, _ := fn.FileLine(pcs[0]) 294 295 idx := pkgIndex(file, fn.Name()) 296 297 runtimePath = file[:idx] 298 if runtime.GOOS == "windows" { 299 runtimePath = strings.ToLower(runtimePath) 300 } 301 } 302 303 func inGoroot(c Call) bool { 304 file := c.file() 305 if len(file) == 0 || file[0] == '?' { 306 return true 307 } 308 if runtime.GOOS == "windows" { 309 file = strings.ToLower(file) 310 } 311 return strings.HasPrefix(file, runtimePath) || strings.HasSuffix(file, "/_testmain.go") 312 } 313 314 // TrimRuntime returns a slice of the CallStack with the topmost entries from 315 // the go runtime removed. It considers any calls originating from unknown 316 // files, files under GOROOT, or _testmain.go as part of the runtime. 317 func (cs CallStack) TrimRuntime() CallStack { 318 for len(cs) > 0 && inGoroot(cs[len(cs)-1]) { 319 cs = cs[:len(cs)-1] 320 } 321 return cs 322 }