这篇文章主要参考了鸟窝的这篇文章,写的很好,自己写一篇防止遗忘
我们在函数有时想获取函数的调用者信息或是整个调用栈,这对于日志记录是很有必要的,但是这些函数的开销会很大,使用时需要谨慎,相关函数如下
- func Caller(skip int) (pc uintptr, file string, line int, ok bool)
- func Callers(skip int, pc []uintptr) int
- func CallersFrames(callers []uintptr) *Frames
- func FuncForPC(pc uintptr) *Func
1. func Caller(skip int) (pc uintptr, file string, line int, ok bool)
这个函数接收一个skip参数,表示调用的深度,0表示调用Caller函数的那个函数,根据skip返回四个值,pc是对应函数的地址,file表示在那个文件,line表示哪行调用的,ok表示存不存在这个skip对应的结果
演示代码如下
func main() {
pc, file, line, ok := runtime.Caller(0)
println(runtime.FuncForPC(pc).Name(), file, line, ok)
// main.main D:/Workstation/Goproject/LearningGo//get_function_names_in_go/demo.go 6 true
// 没有100层深度的调用,所以返回0值
c, file, line, ok := runtime.Caller(100)
println(runtime.FuncForPC(pc).Name(), file, line, ok)
// 0 false
}
2. func Callers(skip int, pc []uintptr) int
刚刚的Caller只会返回制定skip的调用信息,这里的Caller可以根据从你指定的skip开始,将它上面的所以调用信息获取出来,存储到pc中,最后返回的int表示获取到了几个调用信息,这里和鸟窝的文章有点不一样, 鸟窝在文中写道Callers由于历史问题,skip为0时会返回Callers这个函数本身,所以Callers的1才等于调用Callers的那个函数,但是我在试的时候发现Callers这个函数被重复了两遍,不知道为什么,代码如下
func testCallers() {
pcs := make([]uintptr, 10)
n := runtime.Callers(0, pcs)
for i := 0; i < n; i++ {
f := runtime.FuncForPC(pcs[i])
file, line := f.FileLine(pcs[i])
fmt.Printf("%d %s:%d %s\n", i, file, line, f.Name())
}
}
func main() {
Bar()
}
// 下面的这些函数是为了添加调用栈的深度
func Bar() {
Foo()
}
func Foo() {
testCallers()
}
结果如下,不知道为什么runtime.Callers出现了一模一样的两次
3. func CallersFrames(callers []uintptr) * Frames
这个函数接收callers的切片,表示你想获取的函数的地址,然后会直接返回这些函数的信息,省的去一个个调用FuncForPC
func testCallersFrames() {
pc := make([]uintptr, 10) // at least 1 entry needed
n := runtime.Callers(0, pc)
frames := runtime.CallersFrames(pc[:n])
i := 0
for {
frame, more := frames.Next()
fmt.Printf("%d %s:%d %s\n", i, frame.File, frame.Line, frame.Function)
i++
if !more {
break
}
}
}
func main() {
Bar()
}
// 下面的这些函数是为了添加调用栈的深度
func Bar() {
Foo()
}
func Foo() {
// testCallers()
testCallersFrames()
}
结果如下
4. func FuncForPC(pc uintptr) *Func
FuncForPC 是一个有趣的函数, 它可以把程序计数器地址对应的函数的信息获取出来。如果因为内联程序计数器对应多个函数,它返回最外面的函数。
它的返回值是一个*Func类型的值,通过*Func可以获得函数地址、文件行、函数名等信息。
除了上面获取程序计数器的方式,也可以通过反射的方式获取函数的地址:
runtime.FuncForPC(reflect.ValueOf(foo).Pointer()).Name()