比如下面这个例子,我们使用 Caller 方法,将文件名、行号、堆栈函数打印出来:

    1. // 在 prog.go 文件,main 库中调用 call 方法
    2. func call(skip int) bool { //24 行
    3. pc,file,line,ok := runtime.Caller(skip) //25 行
    4. pcName := runtime.FuncForPC(pc).Name() //26 行
    5. fmt.Println(fmt.Sprintf("%v %s %d %s",pc,file,line,pcName)) //27 行
    6. return ok //28 行
    7. } //29 行

    打印出的第一层堆栈函数的信息:

    1. 4821380 /tmp/sandbox064396492/prog.go 25 main.call

    打印堆栈的时候就有这么一个逻辑:先去本地查找是否有这个源代码文件,如果有的话,获取堆栈所在的代码行数,将这个代码行数直接打印到控制台中。

    1. // 打印具体的堆栈信息
    2. func stack(skip int) []byte {
    3. ...
    4. // 循环从第 skip 层堆栈到最后一层
    5. for i := skip; ; i++ {
    6. pc, file, line, ok := runtime.Caller(i)
    7. // 获取堆栈函数所在源文件
    8. if file != lastFile {
    9. data, err := ioutil.ReadFile(file)
    10. if err != nil {
    11. continue
    12. }
    13. lines = bytes.Split(data, []byte{'\n'})
    14. lastFile = file
    15. }
    16. // 打印源代码的内容
    17. fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
    18. }
    19. return buf.Bytes()
    20. }

    这样,打印出来的堆栈信息形如:

    1. /Users/yejianfeng/Documents/gopath/pkg/mod/github.com/gin-gonic/gin@v1.7.2/context.go:165 (0x1385b5a)
    2. (*Context).Next: c.handlers[c.index](c)