【Golang】Go之runtime.Caller/Callers/CallersFrames/FuncForPC使用

skip如果是0,返回当前调用Caller函数的函数名、文件、程序指针PC,1是上一层函数,以此类推:

func Caller(skip int) (pc uintptr, file string, line int, ok bool)
例子
package main

import (
    "log"
    "runtime"
)

func main() {
    test()
}

func test() {
    test2()
}

func test2() {

    // 上2层函数信息
    pc, file, line, ok := runtime.Caller(2)
    // 是否获取成功
    log.Println(ok)
    // 函数指针
    log.Println(pc)
    // 所属文件
    log.Println(file)
    // 所属行
    log.Println(line)
    // 获取函数信息
    f := runtime.FuncForPC(pc)
    // 函数名
    log.Println(f.Name())

    // 当前函数信息
    pc, file, line, ok = runtime.Caller(0)
    log.Println(pc)
    log.Println(file)
    log.Println(line)
    log.Println(ok)
    f = runtime.FuncForPC(pc)
    log.Println(f.Name())

    // 上一层函数信息
    pc, file, line, ok = runtime.Caller(1)
    log.Println(pc)
    log.Println(file)
    log.Println(line)
    log.Println(ok)
    f = runtime.FuncForPC(pc)
    log.Println(f.Name())
}

输出结果为:

2020/04/04 03:54:48 true
2020/04/04 03:54:48 17410619
2020/04/04 03:54:48 /Users/lurongming/main.go
2020/04/04 03:54:48 9
2020/04/04 03:54:48 main.main
2020/04/04 03:54:48 17411178
2020/04/04 03:54:48 /Users/lurongming/main.go
2020/04/04 03:54:48 34
2020/04/04 03:54:48 true
2020/04/04 03:54:48 main.test2
2020/04/04 03:54:48 17410624
2020/04/04 03:54:48 /Users/lurongming/main.go
2020/04/04 03:54:48 13
2020/04/04 03:54:48 true
2020/04/04 03:54:48 main.test

直接从函数指针获取函数名称所属文件:

package main

import (
    "log"
    "reflect"
    "runtime"
)

type Demo struct {
}

func (this *Demo) Hello() {

}

func main() {
    demo := new(Demo)
    pc := reflect.ValueOf(demo.Hello).Pointer()
    f := runtime.FuncForPC(pc)
    log.Println(f.Name())
    log.Println(f.FileLine(pc))
}

输出结果为:

2020/04/04 03:55:47 main.(*Demo).Hello-fm
2020/04/04 03:55:47 /Users/lurongming/main.go 12
func Callers(skip int, pc []uintptr) int

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

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

package main

import (
	"fmt"
	"runtime"
)

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

func Foo() {
	fmt.Printf("我是 %s, %s 在调用我!\n", printMyName(), printCallerName())
	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())
	}
}

func main() {
	Foo()
}

输出结果为:

我是 main.Foo, main.main 在调用我!
我是 main.Bar, main.Foo 又在调用我!
/usr/local/go/src/runtime/extern.go:211 runtime.Callers
/Users/lurongming/main.go:27 main.trace
/Users/lurongming/main.go:24 main.Bar
/Users/lurongming/main.go:20 main.Foo
/Users/lurongming/main.go:37 main.main
/usr/local/go/src/runtime/proc.go:212 runtime.main
/usr/local/go/src/runtime/asm_amd64.s:1358 runtime.goexit
CallersFrames(callers []uintptr) *Frames

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

上面的trace函数可以更改为下面的方式,输出结果是一样的:

func trace() {
	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
		}
	}
}
获取程序堆栈

在程序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
}
参考

golang日志组件使用runtime.Caller性能问题分析

发布了349 篇原创文章 · 获赞 14 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/LU_ZHAO/article/details/105305141