在 Golang 中打印调用栈信息
在日志信息中,有时需要获取当前函数的调用信息,比如打印调用栈:
调用栈
debug.Stack() []byte
debug.Stack()
函数可以获取当前 goroutine 的调用栈信息,需要import "runtime/debug"
。
|
|
可以获得类似输出结果:
--- BEGIN ---
goroutine 1 [running]:
runtime/debug.Stack(0xc00006ef58, 0x1, 0x1)
/usr/local/Cellar/go/1.11.2/libexec/src/runtime/debug/stack.go:24 +0xa7
main.foo()
xxx/trace.go:10 +0x66
main.boom()
xxx/trace.go:15 +0x20
main.main()
xxx/trace.go:19 +0x20
--- END ---
runtime.Stack(buf []byte, all bool) int
debug.Stack()
函数源码在runtime/debug/stack.go
中:
func Stack() []byte {
buf := make([]byte, 1024)
for {
n := runtime.Stack(buf, false)
if n < len(buf) {
return buf[:n]
}
buf = make([]byte, 2*len(buf))
}
}
可以看到它是通过调用runtime.Stack
获取的调用栈信息,
其中第二个参数all
表示是否获取所有 goroutine 的调用栈。
可以将第二个参数改为true
然后创建多个 goroutine 查看输出效果。
更可控的调用信息
上面两个函数虽然可以获取调用栈,但是直接将所有调用信息返回,控制参数较少。
下面几个函数可以实现获取单层的调用信息。
runtime.Caller(skip int) (pc uintptr, file string, line int, ok bool)
runtime.Caller
函数可以获取调用者的信息
-
参数
skip
:表示跳过的层级,相当于往上获取第几层的调用者。0 表示当前函数,1 表示上一层函数,依次往上。 -
返回值
pc
:program counter
(不知道该怎么翻译,程序计数器?),可以大概理解为函数编号,可以通过runtime.FuncForPC
解析file
:文件名line
:行号
|
|
可以得到这样一条输出:
main.boom xxx/trace.go 16
runtime.Callers(skip int, pc []uintptr) int
runtime.Callers
一次可以获取调用栈中多个program counter
。
但是第一个参数skip
与上面的runtime.Caller
稍有不同,如果传0
会获取到Callers
函数本身的信息。
传1
时才与上面的Caller
函数对应。
|
|
输出:
xxx/trace.go 10 main.foo
xxx/trace.go 20 main.boom
xxx/trace.go 24 main.main
/usr/local/Cellar/go/1.11.2/libexec/src/runtime/proc.go 210 runtime.main
/usr/local/Cellar/go/1.11.2/libexec/src/runtime/asm_amd64.s 1334 runtime.goexit
runtime.CallersFrames(callers []uintptr) *runtime.Frames
runtime.CallersFrames
可以一次解析多个pc
|
|
func runtime.FuncForPC(pc uintptr) *runtime.Func
前面使用了runtime.FuncForPC
来解析pc
,获得函数信息。
它返回的*runtime.Func
主要有三个方法:
Entry() uintptr
: 返回函数入口地址(函数注释Entry address of the function)Name() string
: 返回函数名FileLine(pc uintptr) (file string, line int)
: 返回文件名和行号