比如下面这个例子,我们使用 Caller 方法,将文件名、行号、堆栈函数打印出来:
// 在 prog.go 文件,main 库中调用 call 方法
func call(skip int) bool { //24 行
pc,file,line,ok := runtime.Caller(skip) //25 行
pcName := runtime.FuncForPC(pc).Name() //26 行
fmt.Println(fmt.Sprintf("%v %s %d %s",pc,file,line,pcName)) //27 行
return ok //28 行
} //29 行
打印出的第一层堆栈函数的信息:
4821380 /tmp/sandbox064396492/prog.go 25 main.call
打印堆栈的时候就有这么一个逻辑:先去本地查找是否有这个源代码文件,如果有的话,获取堆栈所在的代码行数,将这个代码行数直接打印到控制台中。
// 打印具体的堆栈信息
func stack(skip int) []byte {
...
// 循环从第 skip 层堆栈到最后一层
for i := skip; ; i++ {
pc, file, line, ok := runtime.Caller(i)
// 获取堆栈函数所在源文件
if file != lastFile {
data, err := ioutil.ReadFile(file)
if err != nil {
continue
}
lines = bytes.Split(data, []byte{'\n'})
lastFile = file
}
// 打印源代码的内容
fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
}
return buf.Bytes()
}
这样,打印出来的堆栈信息形如:
/Users/yejianfeng/Documents/gopath/pkg/mod/github.com/gin-gonic/gin@v1.7.2/context.go:165 (0x1385b5a)
(*Context).Next: c.handlers[c.index](c)