The Application Real-Time Monitoring (ARMS) agent for Go provides custom extensions that allow you to inject custom functionality without modifying your source code. This helps you troubleshoot issues by capturing data from request parameters and the request body. This topic uses Net/HTTP as an example to demonstrate how to capture request and response headers with these extensions.
Prerequisites
-
Ensure that your application runs on Go 1.18 or later.
-
Your Go application has been integrated with Application Real-Time Monitoring (ARMS).
ImportantEnsure that the build command is modified to
./instgo go build xxxas specified in the integration documentation.
Limitations
-
You cannot use custom extensions to instrument user-defined functions in
package main. -
You cannot use custom extensions to instrument functions whose ReceiverType is
any.
Code specifications
-
Do not use
package mainin your hook code. -
If you use code from version 2.0.0 or later, you need to add
"_ unsafe"to the import statement, as follows:import ( "encoding/json" "fmt" "github.com/alibaba/loongsuite-go-agent/pkg/api" "net/http" _ "unsafe" ) -
The first parameter of the OnEnter function is always
call api.CallContext, the second parameter is ReceiverType (if any), and the subsequent parameters are the request parameters of the inserted function.NoteIf
ReceiverTypecontains*, you must add the\\prefix in the JSON file to escape characters. -
The first parameter of the
OnExitfunction is fixed ascall api.CallContext, and the subsequent parameters are the return values of the inserted function, specified in sequence.
For example, the net/http::(*Transport).RoundTrip function is as follows:
func (t *Transport) RoundTrip(req *Request) (*Response, error) {
return t.roundTrip(req)
}
Because the RoundTrip ReceiverType is *Transport, the second parameter of the OnEnter function is *Transport and the third parameter is req *Request. The second and third parameters of the OnExit function are the return values of RoundTrip: *Response, error.
If ReceiverType is an unexported type, you can use _ interface{} as a substitute. If there are parameters that you want to ignore, you can also use _ interface{} as a substitute.
Procedure
-
In a directory outside of your current project, create a folder named
rules, initialize it by running thego mod init rulescommand, and then create a file namedrules.goin the folder with the following code.This code defines the custom extension.
package rules import ( "encoding/json" "fmt" "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/api" "net/http" _ "unsafe" ) //go:linkname httpClientEnterHook1 net/http.httpClientEnterHook1 func httpClientEnterHook1(call api.CallContext, t *http.Transport, req *http.Request) { header, _ := json.Marshal(req.Header) fmt.Println("request header is ", string(header)) } //go:linkname httpClientExitHook1 net/http.httpClientExitHook1 func httpClientExitHook1(call api.CallContext, res *http.Response, err error) { header, _ := json.Marshal(res.Header) fmt.Println("response header is ", string(header)) } -
Modify the
config.jsonfile and add the following content to inject the hook code intonet/http::(*Transport).RoundTrip.[ { "ImportPath":"net/http", "Function":"RoundTrip", "OnEnter":"httpClientEnterHook1", "ReceiverType": "\\*Transport", "OnExit": "httpClientExitHook1", "Path": "/extension/rules" // Replace with the absolute path to the rules folder. } ] -
Create a test demo.
In a directory other than the
rulesfolder, create a folder for a demo application and use thego mod init democommand to initialize it. Then, create anet_http.gofile that contains the following code in thedemofolder.package main import ( "context" "net/http" ) func main() { req, err := http.NewRequestWithContext(context.Background(), "GET", "http://www.baidu.com", nil) if err != nil { panic(err) } req.Header.Set("otelbuild", "true") client := &http.Client{} resp, err := client.Do(req) defer resp.Body.Close() } -
Switch to the
demodirectory. Use the instgo tool to build and run the program.$ ./instgo set --rule=../config.json $ INSTGO_CACHE_DIR=./ ./instgo go build net_http.go # If you need to run on a Linux system CGO_ENABLED=0 GOOS=linux GOARCH=amd64 INSTGO_CACHE_DIR=./ ./instgo go build net_http.go $ ./net_httpThe following output confirms that the extension was injected successfully.
request header is {"Otelbuild":["true"]} response header is {"Content-Type":["application/x-gzip"],"Date":["Mon, 04 Nov 2024 09:10:48 GMT"],"Server":["bfe"]}For the complete code example, see nethttp.
NoteIn addition to capturing request and response headers, you can use custom extensions for other purposes, such as SQL injection detection, custom logging, and accessing request and response parameters.