Skip to content

Commit 53c6ea4

Browse files
authored
add context.db.instance to elasticsearch spans (#1673)
* add context.db.instance to elasticsearch spans The cluster name is taken as instance. This only works for Elastic Cloud clusters, as they expose the cluster name in form of a header. closes #1586 * use presence of elastic_transport to select strategy
1 parent b99c027 commit 53c6ea4

File tree

2 files changed

+43
-0
lines changed

2 files changed

+43
-0
lines changed

elasticapm/instrumentation/packages/elasticsearch.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,13 @@ def call(self, module, method, wrapped, instance, args, kwargs):
7171
result = wrapped(*args, **kwargs)
7272
if hasattr(result, "meta"): # elasticsearch-py 8.x+
7373
status_code = result.meta.status
74+
cluster = result.meta.headers.get("x-found-handling-cluster")
7475
else:
7576
status_code = result[0]
77+
cluster = result[1].get("x-found-handling-cluster")
7678
span.context["http"] = {"status_code": status_code}
79+
if cluster:
80+
span.context["db"] = {"instance": cluster}
7781

7882
return result
7983

tests/instrumentation/elasticsearch_tests.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import json
3636
import os
3737
import urllib.parse
38+
from unittest import mock
3839

3940
from elasticsearch import VERSION as ES_VERSION
4041
from elasticsearch import Elasticsearch
@@ -126,6 +127,44 @@ def test_ping(instrument, elasticapm_client, elasticsearch):
126127
assert span["context"]["http"]["status_code"] == 200
127128

128129

130+
@pytest.mark.integrationtest
131+
def test_ping_db_instance(instrument, elasticapm_client, elasticsearch):
132+
# some ugly code to smuggle the "x-found-handling-cluster" header into
133+
# the response from Elasticsearch
134+
try:
135+
import elastic_transport
136+
137+
pool = elasticsearch._transport.node_pool.get().pool
138+
except ImportError:
139+
pool = elasticsearch.transport.connection_pool.get_connection().pool
140+
141+
original_urlopen = pool.urlopen
142+
143+
def wrapper(*args, **kwargs):
144+
result = original_urlopen(*args, **kwargs)
145+
result.headers.update({"x-found-handling-cluster": "foo"})
146+
return result
147+
148+
elasticapm_client.begin_transaction("test")
149+
with mock.patch.object(pool, "urlopen", wraps=wrapper):
150+
result = elasticsearch.ping()
151+
152+
elasticapm_client.end_transaction("test", "OK")
153+
parsed_url = urllib.parse.urlparse(os.environ["ES_URL"])
154+
155+
transaction = elasticapm_client.events[TRANSACTION][0]
156+
spans = elasticapm_client.spans_for_transaction(transaction)
157+
assert len(spans) == 1
158+
span = spans[0]
159+
assert span["name"] == "ES HEAD /"
160+
assert span["context"]["destination"] == {
161+
"address": parsed_url.hostname,
162+
"port": parsed_url.port,
163+
"service": {"name": "", "resource": "elasticsearch/foo", "type": ""},
164+
}
165+
assert span["context"]["db"]["instance"] == "foo"
166+
167+
129168
@pytest.mark.integrationtest
130169
def test_info(instrument, elasticapm_client, elasticsearch):
131170
elasticapm_client.begin_transaction("test")

0 commit comments

Comments
 (0)