diff --git a/module/apmelasticsearch/client.go b/module/apmelasticsearch/client.go index a9868feb4..4e97f04ec 100644 --- a/module/apmelasticsearch/client.go +++ b/module/apmelasticsearch/client.go @@ -92,7 +92,18 @@ func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { Name: "elasticsearch", Resource: "elasticsearch", }) + + clusterName := req.Header.Get("x-found-handling-cluster") + if clusterName == "" { + clusterName = req.URL.Host + } + + span.Context.SetServiceTarget(apm.ServiceTargetSpanContext{ + Type: "elasticsearch", + Name: clusterName, + }) span.Context.SetDatabase(apm.DatabaseSpanContext{ + Instance: clusterName, Type: "elasticsearch", Statement: statement, User: username, diff --git a/module/apmelasticsearch/client_test.go b/module/apmelasticsearch/client_test.go index 4a7f8df52..e571635cf 100644 --- a/module/apmelasticsearch/client_test.go +++ b/module/apmelasticsearch/client_test.go @@ -24,8 +24,10 @@ import ( "errors" "io" "io/ioutil" + "net" "net/http" "net/http/httptest" + "strconv" "strings" "testing" "time" @@ -66,11 +68,13 @@ func TestWrapRoundTripper(t *testing.T) { assert.Equal(t, "q=user:kimchy", spans[0].Context.HTTP.URL.RawQuery) assert.Equal(t, &model.DatabaseSpanContext{ + Instance: strings.TrimPrefix(server.URL, "http://"), Type: "elasticsearch", Statement: "user:kimchy", }, spans[0].Context.Database) assert.Equal(t, &model.DatabaseSpanContext{ + Instance: strings.TrimPrefix(server.URL, "http://"), Type: "elasticsearch", Statement: `query":{term":{"user":"kimchy"}}`, User: "Aladdin", @@ -171,6 +175,7 @@ func testStatementGetBody(t *testing.T, path string) { require.Len(t, spans, 1) assert.Equal(t, &model.DatabaseSpanContext{ + Instance: strings.TrimPrefix(server.URL, "http://"), Type: "elasticsearch", Statement: "Request.GetB", // limited to Content-Length }, spans[0].Context.Database) @@ -194,6 +199,7 @@ func TestStatementGetBodyErrors(t *testing.T) { require.Len(t, spans, 1) assert.Equal(t, &model.DatabaseSpanContext{ + Instance: strings.TrimPrefix(server.URL, "http://"), Type: "elasticsearch", Statement: "", // GetBody/reader returned an error }, spans[0].Context.Database) @@ -242,6 +248,7 @@ func TestStatementBodyReadError(t *testing.T) { require.Len(t, spans, 1) assert.Equal(t, &model.DatabaseSpanContext{ + Instance: "testing.invalid", Type: "elasticsearch", Statement: "", // req.Body.Read returned an error }, spans[0].Context.Database) @@ -269,6 +276,7 @@ func TestStatementBodyGzipContentEncoding(t *testing.T) { require.Len(t, spans, 1) assert.Equal(t, &model.DatabaseSpanContext{ + Instance: strings.TrimPrefix(server.URL, "http://"), Type: "elasticsearch", Statement: "decoded", }, spans[0].Context.Database) @@ -306,6 +314,39 @@ func TestDestination(t *testing.T) { test("http://[2001:db8::1]:80/_search", "2001:db8::1", 80) } +func TestServiceTarget(t *testing.T) { + var rt roundTripperFunc = func(req *http.Request) (*http.Response, error) { + return httptest.NewRecorder().Result(), nil + } + client := &http.Client{Transport: apmelasticsearch.WrapRoundTripper(rt)} + + test := func(url, destinationAddr string, destinationPort int, clusterName string) { + req, err := http.NewRequest("GET", url, nil) + req.Header.Add("x-found-handling-cluster", clusterName) + require.NoError(t, err) + _, spans, _ := apmtest.WithTransaction(func(ctx context.Context) { + resp, err := client.Do(req.WithContext(ctx)) + assert.NoError(t, err) + resp.Body.Close() + }) + require.Len(t, spans, 1) + if clusterName == "" { + clusterName = net.JoinHostPort(destinationAddr, strconv.Itoa(destinationPort)) + } + assert.Equal(t, &model.ServiceSpanContext{ + Target: &model.ServiceTargetSpanContext{ + Type: "elasticsearch", + Name: clusterName, + }, + }, spans[0].Context.Service) + } + test("http://host:9200/_search", "host", 9200, "foo") + test("http://host:80/_search", "host", 80, "bar") + test("http://127.0.0.1:9200/_search", "127.0.0.1", 9200, "baz") + test("http://[2001:db8::1]:9200/_search", "2001:db8::1", 9200, "foobar") + test("http://[2001:db8::1]:80/_search", "2001:db8::1", 80, "") +} + func TestTraceHeaders(t *testing.T) { headers := make(map[string]string) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { diff --git a/module/apmelasticsearch/internal/integration/elastic_integration_test.go b/module/apmelasticsearch/internal/integration/elastic_integration_test.go index 73317f63c..0b74aab9a 100644 --- a/module/apmelasticsearch/internal/integration/elastic_integration_test.go +++ b/module/apmelasticsearch/internal/integration/elastic_integration_test.go @@ -71,9 +71,16 @@ func TestElastic(t *testing.T) { assert.Equal(t, "", spans[0].Action) assert.Equal(t, &model.SpanContext{ Database: &model.DatabaseSpanContext{ + Instance: esurl.Host, Type: "elasticsearch", Statement: `{"query":{"match_all":{}}}`, }, + Service: &model.ServiceSpanContext{ + Target: &model.ServiceTargetSpanContext{ + Type: "elasticsearch", + Name: esurl.Host, + }, + }, HTTP: &model.HTTPSpanContext{ URL: esurl, StatusCode: 404, diff --git a/module/apmelasticsearch/internal/integration/olivere_integration_test.go b/module/apmelasticsearch/internal/integration/olivere_integration_test.go index 49a3d8478..dbbacb08d 100644 --- a/module/apmelasticsearch/internal/integration/olivere_integration_test.go +++ b/module/apmelasticsearch/internal/integration/olivere_integration_test.go @@ -77,6 +77,7 @@ func TestOlivereElastic(t *testing.T) { assert.Equal(t, "", spans[0].Action) assert.Equal(t, &model.SpanContext{ Database: &model.DatabaseSpanContext{ + Instance: esurl.Host, Type: "elasticsearch", Statement: `{"query":{"match_all":{}}}`, }, @@ -89,6 +90,12 @@ func TestOlivereElastic(t *testing.T) { Resource: "elasticsearch", }, }, + Service: &model.ServiceSpanContext{ + Target: &model.ServiceTargetSpanContext{ + Type: "elasticsearch", + Name: esurl.Host, + }, + }, HTTP: &model.HTTPSpanContext{ URL: esurl, StatusCode: 404,