diff --git a/changes/40564-otel-http-client b/changes/40564-otel-http-client new file mode 100644 index 0000000000..178697f3fe --- /dev/null +++ b/changes/40564-otel-http-client @@ -0,0 +1 @@ +* Added OTEL instrumentation to Fleet's internal HTTP client. diff --git a/pkg/fleethttp/fleethttp.go b/pkg/fleethttp/fleethttp.go index 167f3e297a..f7f1392b8c 100644 --- a/pkg/fleethttp/fleethttp.go +++ b/pkg/fleethttp/fleethttp.go @@ -12,6 +12,7 @@ import ( "os" "time" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "golang.org/x/oauth2" ) @@ -71,9 +72,11 @@ func NewClient(opts ...ClientOpt) *http.Client { if co.noFollow { cli.CheckRedirect = noFollowRedirect } + var baseTransport http.RoundTripper if co.tlsConf != nil { - cli.Transport = NewTransport(WithTLSConfig(co.tlsConf)) + baseTransport = NewTransport(WithTLSConfig(co.tlsConf)) } + cli.Transport = otelhttp.NewTransport(baseTransport) if co.cookieJar != nil { cli.Jar = co.cookieJar } @@ -125,11 +128,13 @@ func noFollowRedirect(*http.Request, []*http.Request) error { // token for authentication (as OAuth2 static token). func NewGithubClient() *http.Client { if githubToken := os.Getenv("NETWORK_TEST_GITHUB_TOKEN"); githubToken != "" { - return oauth2.NewClient(context.Background(), oauth2.StaticTokenSource( + cli := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource( &oauth2.Token{ AccessToken: githubToken, }, )) + cli.Transport = otelhttp.NewTransport(cli.Transport) + return cli } return NewClient() } diff --git a/pkg/fleethttp/fleethttp_test.go b/pkg/fleethttp/fleethttp_test.go index 250126dde6..79dc901dcd 100644 --- a/pkg/fleethttp/fleethttp_test.go +++ b/pkg/fleethttp/fleethttp_test.go @@ -3,18 +3,21 @@ package fleethttp import ( "crypto/tls" "net/http" + "reflect" "testing" "time" + "unsafe" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func TestClient(t *testing.T) { cases := []struct { name string opts []ClientOpt - nilTransport bool + defaultInner bool nilRedirect bool timeout time.Duration }{ @@ -31,10 +34,14 @@ func TestClient(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { cli := NewClient(c.opts...) - if c.nilTransport { - assert.Nil(t, cli.Transport) + require.IsType(t, &otelhttp.Transport{}, cli.Transport, "outer transport should be otelhttp") + // Inspect the inner (base) transport wrapped by otelhttp via unsafe since the rt field is unexported. + rtField := reflect.ValueOf(cli.Transport).Elem().FieldByName("rt") + inner := *(*http.RoundTripper)(unsafe.Pointer(rtField.UnsafeAddr())) //nolint:gosec + if c.defaultInner { + assert.Equal(t, http.DefaultTransport, inner, "inner transport should be http.DefaultTransport") } else { - assert.NotNil(t, cli.Transport) + assert.IsType(t, &http.Transport{}, inner, "inner transport should be a custom *http.Transport") //nolint:gocritic } if c.nilRedirect { assert.Nil(t, cli.CheckRedirect)