首页 > 其他分享 >golang 实现一个自动注入跟踪代码工具

golang 实现一个自动注入跟踪代码工具

时间:2023-05-13 18:36:46浏览次数:30  
标签:runtime 函数 代码 Goroutine golang exit 跟踪 func main

如下面代码所示:

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

相关文章