在golang中,我们可以通过go官方提供的runtime包中的 Caller函数获取是那个文件在哪一行调用了当前函数, 使用Callers, CallersFrames函数,我们可以获取完整的函数调用堆栈信息, 包括 调用函数所在的源码文件, 行号, 函数等信息。 这些信息在我们排查异常和解决异常信息时非常有用,有了这个信息,我们就可以快速定位问题发生的源文件和行号。
runtime.Caller 获取调用函数/方法所在的源代码文件路径和行号
Caller函数定义 func runtime.Caller(skip int) (pc uintptr, file string, line int, ok bool)
注意这里的参数 skip 这个值需要根据你当前的函数调用方式来确定, 如果这个函数是被直接调用,则这里直接使用 1 就可以, 如果是间接调用,则根据间接调用的层级来设置,一般为 2 (如在异常捕获里面我们使用 runtime.Caller来获取异常发生的源文件和行号skip就应该是 2 )
Example 直接调用 使用示例
package demo1_test
import (
"fmt"
"runtime"
)
func ExampleCaller() {
callTest() // 调用这个函数 会输出当前文件名,和行号 9
// output: file: /Volumes/data/projects/go/my_project/gocms/webserver/demo1/caller_test.go , line: 9 , funcName: gotms/demo1_test.ExampleCaller
}
func callTest() {
// 获取调用源文件 和行号, 特别注意这里的参数
// 0 表示runtime.Caller自己, 1 是调当前函数的那个函数,
// 这里的skip参数需要根据你的函数的调用方式,如果通过中转调用(如在异常捕获函数recover()里面 那么skip就应该是 2 ),则转换一次加1
pc, file, line, ok := runtime.Caller(1)
// 如果获取不成功
if !ok {
panic("Caller error!")
}
// 到这里的 file, line 就是调用当前函数的源文件的路径和行号
// 如果我们还需要获取是那个函数调用,可以使用上面的pc作为参数,通过函数 FuncForPC(pc).Name() 获取
funcName := runtime.FuncForPC(pc).Name() // 调用函数名 如: gotms/app/common/middleware.AuthHandler
fmt.Printf("file: %v , line: %d , funcName: %v ", file, line, funcName)
}
间接调用时runtime.Caller使用示例
当前文件路径为 /opt/demo1/caller_test.go 异常发生的行号 第43行, 这里因为是在defer函数里面间接调用runtime.Caller , 所以这里的skip 就应该是 2 , 而不是1
runtime.Callers, CallersFrames获取调用堆栈中的调用源文件和行号
获取调用堆栈中的调用源文件和行号的方法就是先创建一个[]uintptr切片,这个切片的大小决定了你最多能够获取多少层的堆栈信息! 然后使用函数runtime.Callers来获取对应的堆栈的指针, 然后再利用CallersFrames获取*runtime.Frames对象,这个对象就一个方法 Next() 所以需要采用for无限循环来获取全部的调用堆栈信息
获取完整堆栈Example测试用例
func ExampleCallStacks() {
rpc := make([]uintptr, 10) // 定义一个切片用来存放调用堆栈的指针 这里10 就表示最多获取10个层级的调用堆栈信息
runtime.Callers(0, rpc) // 获取调用堆栈的uintptr指针 rpc
frames := runtime.CallersFrames(rpc) // 获取调用堆栈的frames指针信息
// 这个frames是一个指针,只有Next这个方法,所以就使用for无限循环模式来获取数据
for {
if f, more := frames.Next(); more {
fmt.Printf("file:%v, function:%v line:%v\n", f.File, f.Function, f.Line)
} else {
break // 退出这个无限循环, 这里不退出的话就死循环了哦!
}
}
// output: file:/go/src/runtime/extern.go, function:runtime.Callers line:325
// file:/opt/demo1/caller_test.go, function:gotms/demo1_test.ExampleCallStacks line:48
// file:/go/src/testing/run_example.go, function:testing.runExample line:63
// file:/go/src/testing/example.go, function:testing.runExamples line:40
// file:/go/src/testing/testing.go, function:testing.(*M).Run line:2029
// file:_testmain.go, function:main.main line:59
// file:/go/src/runtime/proc.go, function:runtime.main line:271
}
注意:上面的测试用例,在不同的机器上面后面的 output: 的内容可能不一样 主要是相应的源文件路径不一样!