golang 获取调用函数的源文件路径和所在行号, 获取异常发生的源文件路径和行号方法 -- runtime.Caller, Callers, CallersFrames获取调用堆栈函数使用与总结

在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: 的内容可能不一样 主要是相应的源文件路径不一样!

 

总结:要获取调用源文件的路径和行号,这里的skip的值非常关键,从上面的示例中我们也能看出,Caller的skip的值 0表示他本身,1表示调用他的那个函数或者方法所在的源文件,直接调用skip为1,如果是间接调用则skip一般为2, 当然还有特殊情况, 如你对某个库进行了二次封装,这个库原来就是使用的间接调用方式,那么你二次封装后这个skip就应该是3.  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值