使用Go Agent的自定义扩展功能,可以在不修改原有代码的情况下,通过编译时插桩的能力完成代码注入,以下介绍编写自定义扩展的流程规范。
实施流程
一、创建一个文件夹hook(名称可自定义)
mkdir hook
go mod init hook此处不需要go mod tidy来同步。
二、编写注入规则文件
以http的demo作为示例,在http的client的RoundTrip 注入代码。

规则如下所示,编写完成后保存到json文件中,将文件放到编译时候能找到的目录即可,如demo/rule.json。
[
  {
    "ImportPath":"net/http", //必填项,注入的函数所在的完整包路径,如net/http,这里可以为github.com,也可以是自己sdk的包路径
    "Function":"RoundTrip",  //必填项,表示你需要注入的函数是什么,这里是RoundTrip
    "OnEnter":"httpClientEnter", //非必填,表示在RoundTrip函数的开始添加的执行函数名称,如httpClientEnter
    "ReceiverType": "\\*Transport", //非必填,表示这个RoundTrip函数属于哪个类,这里属于*Transport
    "OnExit": "httpClientExit", //非必填,表示在RoundTrip函数的执行后defer添加的执行函数名称,如httpClientExit
    "Path": "/extension/rules" //必填项,表示上述创建的hook代码所在目录的路径
  }
]三、编写hook代码:
hook文件夹下的代码package不能为main。
hook.go文件的import中添加
_ "unsafe"。
示例代码如下所示:
package rules
import (
	"encoding/json"
	"fmt"
	"github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/api"
	"net/http"
         _ "unsafe"
)
//go:linkname httpClientEnter net/http.httpClientEnter
func httpClientEnter(call api.CallContext, t *http.Transport, req *http.Request) {
	header, _ := json.Marshal(req.Header)
	fmt.Println("request header is ", string(header))
    temp := make(map[string]interface{}, 1)
	temp["test"] = "test"
	call.SetData(temp)
    req.Header.Add("test", "test")
    call.SetParam(1, req)
}
//go:linkname httpClientExit net/http.httpClientExit
func httpClientExit(call api.CallContext, res *http.Response, err error) {
	header, _ := json.Marshal(res.Header)
	fmt.Println("response header is ", string(header))
    temp := call.GetData().(map[string]interface{})
	t := temp["test"].(string)
    fmt.Println(t)
    err = errors.New("test")
	call.SetReturnVal(1, err)
}字段说明:
//go:linkname httpClientEnter net/http.httpClientEnter这个注释是必填,httpClientEnter是函数名称,net/http是importPath。onEnter函数编写:第一个参数固定call api.CallContext,第二个参数是类名称,如*http.Transport,后续参数为RoundTrip这个函数的入参。若函数不属于任何类,则第二个参数开始就是函数的入参,如httpClientEnter。onExit函数编写:第一个参数固定call api.CallContext,从第二个参数开始填RoundTrip的返回值,若该函数没有返回值,则无需填写。
四、其他说明
目前不支持对泛型函数进行注入。
多个不相关的hook代码,规则可以放到同一个json,不同sdk的hook代码,不要放到同一个目录。
如果注入的函数类名是小写的,未对外暴露,在写
onEnter函数的时候,第二个函数写成类似r interface{}这样,用interface{}替换类名称,其他的比如参数名称小写也按同样方式处理。如果需要在
onEnter和onExit函数间传递信息,可以通过如上代码中SetData的方式传递。如果需要修改函数某个参数,可以在
onEnter函数通过如下方式设置,这里在Header中添加了kv,然后通过call.SetParam将修改后的req 设置回去,这里call.SetParam的第一个参数是序号,即这个修改的参数在哪个位置,第二个参数从0开始数,这里req 在1的位置。
req.Header.Add("test", "test")
call.SetParam(1, req)如果想修改函数的返回值,可以通过以下方式实现:
err = errors.New("test")
call.SetReturnVal(1, err)call.SetReturnVal设置返回值,call.SetReturnVal第一个参数也是修改参数的位置,第二个参数从0开始数,这里err 在1的位置。