如何在Go的函数中得到调用者函数名?

栏目: Go · 发布时间: 5年前

内容简介:有时候在Go的函数调用的过程中,我们需要知道函数被谁调用,比如打印日志信息等。例如下面的函数,我们希望在日志中打印出调用者的名字。最简单的方式就是硬编码。 因为在编译之前,我们肯定知道打印的时候所在哪个函数,但是更好的方式是编写一个通用的函数,比如下面的例子:输出结果:

有时候在 Go 的函数调用的过程中,我们需要知道函数被谁调用,比如打印日志信息等。例如下面的函数,我们希望在日志中打印出调用者的名字。

func Foo() {
	fmt.Println("谁在调用我?")
	bar()
}

func Bar() {
	fmt.Println("谁又在调用我?")
}

首先打印函数本身的名称

最简单的方式就是硬编码。 因为在编译之前,我们肯定知道打印的时候所在哪个函数,但是更好的方式是编写一个通用的函数,比如下面的例子:

package main

import (
	"fmt"
	"runtime"
)

func main() {
	Foo()
}

func Foo() {
	fmt.Printf("我是 %s, 谁在调用我?\n", printMyName())
	Bar()
}

func Bar() {
	fmt.Printf("我是 %s, 谁又在调用我?\n", printMyName())
}

func printMyName() string {
	pc, _, _, _ := runtime.Caller(1)
	return runtime.FuncForPC(pc).Name()
}

输出结果:

我是 main.Foo, 谁在调用我?
我是 main.Bar, 谁又在调用我?

可以看到函数在被调用的时候, printMyName 把函数本身的名字打印出来了,注意这里 Caller 的参数是1, 因为我们将业务代码封装成了一个函数。

首先打印函数调用者的名称

将上面的代码修改一下,增加一个新的 printCallerName 的函数,可以打印调用者的名称。

func main() {
	Foo()
}

func Foo() {
	fmt.Printf("我是 %s, %s 在调用我!\n", printMyName(), printCallerName())
	Bar()
}

func Bar() {
	fmt.Printf("我是 %s, %s 又在调用我!\n", printMyName(), printCallerName())
}

func printMyName() string {
	pc, _, _, _ := runtime.Caller(1)
	return runtime.FuncForPC(pc).Name()
}

func printCallerName() string {
	pc, _, _, _ := runtime.Caller(2)
	return runtime.FuncForPC(pc).Name()
}

相关函数介绍

你可以通过 runtime.Callerruntime.Callersruntime.FuncForPC 等函数更详细的跟踪函数的调用堆栈。

func Caller(skip int) (pc uintptr, file string, line int, ok bool)

Caller 可以返回函数调用栈的某一层的程序计数器、文件信息、行号。

0 代表当前函数,也是调用 runtime.Caller 的函数。1 代表上一层调用者,以此类推。

func Callers(skip int, pc []uintptr) int

Callers 用来返回调用站的程序计数器, 放到一个uintptr中。

0 代表 Callers 本身,这和上面的 Caller 的参数的意义不一样,历史原因造成的。 1 才对应这上面的 0。

比如在上面的例子中增加一个 trace 函数,被函数 Bar 调用。

……
func Bar() {
	fmt.Printf("我是 %s, %s 又在调用我!\n", printMyName(), printCallerName())
	trace()
}

func trace() {
	pc := make([]uintptr,10) // at least 1 entry needed
	n := runtime.Callers(0, pc)
	for i :=0; i < n; i++ {
		f := runtime.FuncForPC(pc[i])
		file, line := f.FileLine(pc[i])
		fmt.Printf("%s:%d %s\n", file, line, f.Name())
	}
}

输出结果可以看到这个goroutine的整个栈都打印出来了:

/usr/local/go/src/runtime/extern.go:218 runtime.Callers
/Users/yuepan/go/src/git.intra.weibo.com/platform/tool/g/main.go:34 main.trace
/Users/yuepan/go/src/git.intra.weibo.com/platform/tool/g/main.go:20 main.Bar
/Users/yuepan/go/src/git.intra.weibo.com/platform/tool/g/main.go:15 main.Foo
/Users/yuepan/go/src/git.intra.weibo.com/platform/tool/g/main.go:10 main.main
/usr/local/go/src/runtime/proc.go:210 runtime.main
/usr/local/go/src/runtime/asm_amd64.s:1334 runtime.goexit

func CallersFrames(callers []uintptr) *Frames

上面的 Callers 只是或者栈的程序计数器,如果想获得整个栈的信息,可以使用 CallersFrames 函数,省去遍历调用 FuncForPC

上面的 trace 函数可以更改为下面的方式:

func trace2() {
	pc := make([]uintptr,10) // at least 1 entry needed
	n := runtime.Callers(0, pc)
	frames := runtime.CallersFrames(pc[:n])
	for {
		frame, more := frames.Next()
		fmt.Printf("%s:%d %s\n", frame.File, frame.Line, frame.Function)
		if !more {
			break
		}
	}
}

func FuncForPC(pc uintptr) *Func

FuncForPC 是一个有趣的函数, 它可以把程序计数器地址对应的函数的信息获取出来。如果因为内联程序计数器对应多个函数,它返回最外面的函数。

它的返回值是一个 *Func 类型的值,通过 *Func 可以获得函数地址、文件行、函数名等信息。

除了上面获取程序计数器的方式,也可以通过反射的方式获取函数的地址:

runtime.FuncForPC(reflect.ValueOf(foo).Pointer()).Name()

获取程序堆栈

在程序panic的时候,一般会自动把堆栈打出来,如果你想在程序中获取堆栈信息,可以通过 debug.PrintStack() 打印出来。比如你在程序中遇到一个Error,但是不期望程序panic,只是想把堆栈信息打印出来以便跟踪调试,你可以使用 debug.PrintStack()

抑或,你自己读取堆栈信息,自己处理和打印:

func DumpStacks() {
	buf:=make([]byte,16384)
	buf = buf[:runtime.Stack(buf, true)]
	fmt.Printf("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf)
}

参考 调试利器:dump goroutine 的 stacktrace

利用堆栈信息还可以获取goroutine的id, 参考: 再谈谈获取 goroutine id 的方法

func GoID() int {
	var buf [64]byte
	n := runtime.Stack(buf[:], false)
	idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
	id, err := strconv.Atoi(idField)
	if err != nil {
		panic(fmt.Sprintf("cannot get goroutine id: %v", err))
	}
	return id
}

以上所述就是小编给大家介绍的《如何在Go的函数中得到调用者函数名?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Art of Computer Programming, Volumes 1-3 Boxed Set

The Art of Computer Programming, Volumes 1-3 Boxed Set

Donald E. Knuth / Addison-Wesley Professional / 1998-10-15 / USD 199.99

This multivolume work is widely recognized as the definitive description of classical computer science. The first three volumes have for decades been an invaluable resource in programming theory and p......一起来看看 《The Art of Computer Programming, Volumes 1-3 Boxed Set》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具