All Products
Search
Document Center

Application Real-Time Monitoring Service:Use custom extensions for the Go agent

Last Updated:Jun 21, 2026

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).

    Important

    Ensure that the build command is modified to ./instgo go build xxx as 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 main in 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.

    Note

    If ReceiverType contains *, you must add the \\ prefix in the JSON file to escape characters.

  • The first parameter of the OnExit function is fixed as call 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

  1. In a directory outside of your current project, create a folder named rules, initialize it by running the go mod init rules command, and then create a file named rules.go in 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))
    }
  2. Modify the config.json file and add the following content to inject the hook code into net/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.
      }
    ]
  3. Create a test demo.

    In a directory other than the rules folder, create a folder for a demo application and use the go mod init demo command to initialize it. Then, create a net_http.go file that contains the following code in the demo folder.

    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()
    }
    
  4. Switch to the demo directory. 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_http

    The 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.

    Note

    In 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.