这篇文章主要参考了鸟窝的这篇文章,写的很好,自己写一篇防止遗忘

我们在函数有时想获取函数的调用者信息或是整个调用栈,这对于日志记录是很有必要的,但是这些函数的开销会很大,使用时需要谨慎,相关函数如下

  • 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()