如下面代码所示:
package main import ( "bytes" "fmt" "runtime" "strconv" ) /** 实现一个自动注入跟踪代码,并输出有层次感的函数调用链跟踪命令行工具. */ func Trace() func() { //通过 runtime.Caller 函数获得当前 Goroutine 的函数调用栈上的信息, // runtime.Caller 的参数标识的是要获取的是哪一个栈帧的信息。 //当参数为 0时,返回的是 Caller 函数的调用者的函数信息,在这里就是 Trace 函数。 // 但我们需要的是Trace 函数的调用者的信息,于是我们传入 1。 //Caller 函数有四个返回值: // 第一个返回值代表的是程序计数(pc); // 第二个和第三个参数代表对应函数所在的源文件名以及所在行数,这里我们暂时不需要; // 最后一个参数代表是否能成功获取这些信息,如果获取失败,我们抛出 panic。 pc, _, _, ok := runtime.Caller(1) if !ok { panic("not found caller") } //通过 runtime.FuncForPC 函数和程序计数器(PC)得到被跟踪函数的函数名称 //runtime.FuncForPC 返回的名称中不仅仅包含函数名,还包含了被跟踪函数所在的包名。 fn := runtime.FuncForPC(pc) name := fn.Name() //增加 增加 Goroutine 标识 前: //println("enter:", name) //return func() { // println("exit:", name) //} // 增加 Goroutine 标识 后: gid := curGoroutineID() fmt.Printf("g[%05d]: enter: [%s]\n", gid, name) //输出的 Goroutine ID 为 5 位数字,如果 ID 值不足 5 位,则左补零,这一切都是 Printf 函数的格式控制字符串“%05d”帮助我们实现的。 return func() { fmt.Printf("g[%05d]: exit: [%s]\n", gid, name) } } func foo() { defer Trace()() bar() } func bar() { defer Trace()() } func main() { defer Trace()() foo() /* enter: main.main enter: main.foo enter: main.bar exit: main.bar exit: main.foo exit: main.main ---------------------- g[00001]: enter: [main.main] g[00001]: enter: [main.foo] g[00001]: enter: [main.bar] g[00001]: exit: [main.bar] g[00001]: exit: [main.foo] g[00001]: exit: [main.main] */ } /* 第二个问题,也就是当程序中有多 Goroutine 时,Trace 输出的跟踪信息混杂在一起难以分辨的问题。 解决办法:增加 Goroutine 标识. 在输出的函数出入口信息时,带上一个在程序每次执行时能唯一区分Goroutine 的 Goroutine ID。 个获取 Goroutine ID 的标准方法, 参考: // $GOROOT/src/net/http/h2_bundle.go */ var goroutineSpace = []byte("goroutine ") func curGoroutineID() uint64 { b := make([]byte, 64) b = b[:runtime.Stack(b, false)] // Parse the 4707 out of "goroutine 4707 [" b = bytes.TrimPrefix(b, goroutineSpace) i := bytes.IndexByte(b, ' ') if i < 0 { panic(fmt.Sprintf("No space found in %q", b)) } b = b[:i] n, err := strconv.ParseUint(string(b), 10, 64) if err != nil { panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err)) } return n }
标签:runtime,函数,代码,Goroutine,golang,exit,跟踪,func,main From: https://www.cnblogs.com/rxbook/p/17397888.html