elasticsearch: don't produce spans if native elasticsearch support is enabled (#2524)

This commit is contained in:
Riccardo Magliocchetti
2024-05-24 22:13:37 +02:00
committed by GitHub
parent 65b4f850a0
commit eb8e45695e
5 changed files with 66 additions and 7 deletions

View File

@ -54,10 +54,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#2474](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2474)) ([#2474](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2474))
- `opentelemetry-instrumentation-elasticsearch` Improved support for version 8 - `opentelemetry-instrumentation-elasticsearch` Improved support for version 8
([#2420](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2420)) ([#2420](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2420))
- `opentelemetry-instrumentation-elasticsearch` Disabling instrumentation with native OTel support enabled
([#2524](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2524))
- `opentelemetry-instrumentation-asyncio` Check for __name__ attribute in the coroutine - `opentelemetry-instrumentation-asyncio` Check for __name__ attribute in the coroutine
([#2521](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2521)) ([#2521](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2521))
- `opentelemetry-util-http` Preserve brackets around literal IPv6 hosts ([#2552](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2552)) - `opentelemetry-util-http` Preserve brackets around literal IPv6 hosts ([#2552](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2552))
## Version 1.24.0/0.45b0 (2024-03-28) ## Version 1.24.0/0.45b0 (2024-03-28)
### Added ### Added

View File

@ -16,6 +16,15 @@
This library allows tracing HTTP elasticsearch made by the This library allows tracing HTTP elasticsearch made by the
`elasticsearch <https://elasticsearch-py.readthedocs.io/en/master/>`_ library. `elasticsearch <https://elasticsearch-py.readthedocs.io/en/master/>`_ library.
.. warning::
The elasticsearch package got native OpenTelemetry support since version
`8.13 <https://www.elastic.co/guide/en/elasticsearch/client/python-api/current/release-notes.html#rn-8-13-0>`_.
To avoid duplicated tracing this instrumentation disables itself if it finds an elasticsearch client
that has OpenTelemetry support enabled.
Please be aware that the two libraries may use a different semantic convention, see
`elasticsearch documentation <https://www.elastic.co/guide/en/elasticsearch/client/python-api/current/opentelemetry.html>`_.
Usage Usage
----- -----
@ -54,7 +63,7 @@ def response_hook(span: Span, response: dict)
for example: for example:
.. code: python .. code-block: python
from opentelemetry.instrumentation.elasticsearch import ElasticsearchInstrumentor from opentelemetry.instrumentation.elasticsearch import ElasticsearchInstrumentor
import elasticsearch import elasticsearch
@ -81,6 +90,7 @@ API
""" """
import re import re
import warnings
from logging import getLogger from logging import getLogger
from os import environ from os import environ
from typing import Collection from typing import Collection
@ -197,6 +207,16 @@ def _wrap_perform_request(
): ):
# pylint: disable=R0912,R0914 # pylint: disable=R0912,R0914
def wrapper(wrapped, _, args, kwargs): def wrapper(wrapped, _, args, kwargs):
# if wrapped elasticsearch has native OTel instrumentation just call the wrapped function
otel_span = kwargs.get("otel_span")
if otel_span and otel_span.otel_span:
warnings.warn(
"Instrumentation disabled, relying on elasticsearch native OTel support, see "
"https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/elasticsearch/elasticsearch.html",
Warning,
)
return wrapped(*args, **kwargs)
method = url = None method = url = None
try: try:
method, url, *_ = args method, url, *_ = args
@ -249,6 +269,11 @@ def _wrap_perform_request(
v = str(v) v = str(v)
elif isinstance(v, elastic_transport.HttpHeaders): elif isinstance(v, elastic_transport.HttpHeaders):
v = dict(v) v = dict(v)
elif isinstance(
v, elastic_transport.OpenTelemetrySpan
):
# the transport Span is always a dummy one
v = None
return (k, v) return (k, v)
hook_kwargs = dict( hook_kwargs = dict(

View File

@ -1,9 +1,9 @@
asgiref==3.7.2 asgiref==3.7.2
attrs==23.2.0 attrs==23.2.0
Deprecated==1.2.14 Deprecated==1.2.14
elasticsearch==8.12.1 elasticsearch==8.13.1
elasticsearch-dsl==8.12.0 elasticsearch-dsl==8.13.1
elastic-transport==8.12.0 elastic-transport==8.13.0
importlib-metadata==6.11.0 importlib-metadata==6.11.0
iniconfig==2.0.0 iniconfig==2.0.0
packaging==23.2 packaging==23.2

View File

@ -23,6 +23,7 @@ import elasticsearch
import elasticsearch.exceptions import elasticsearch.exceptions
from elasticsearch import Elasticsearch from elasticsearch import Elasticsearch
from elasticsearch_dsl import Search from elasticsearch_dsl import Search
from pytest import mark
import opentelemetry.instrumentation.elasticsearch import opentelemetry.instrumentation.elasticsearch
from opentelemetry import trace from opentelemetry import trace
@ -36,7 +37,7 @@ from opentelemetry.trace import StatusCode
from . import sanitization_queries # pylint: disable=no-name-in-module from . import sanitization_queries # pylint: disable=no-name-in-module
major_version = elasticsearch.VERSION[0] major_version, minor_version = elasticsearch.VERSION[:2]
if major_version == 8: if major_version == 8:
from . import helpers_es8 as helpers # pylint: disable=no-name-in-module from . import helpers_es8 as helpers # pylint: disable=no-name-in-module
@ -70,6 +71,9 @@ def get_elasticsearch_client(*args, **kwargs):
@mock.patch(helpers.perform_request_mock_path) @mock.patch(helpers.perform_request_mock_path)
@mock.patch.dict(
os.environ, {"OTEL_PYTHON_INSTRUMENTATION_ELASTICSEARCH_ENABLED": "false"}
)
class TestElasticsearchIntegration(TestBase): class TestElasticsearchIntegration(TestBase):
search_attributes = { search_attributes = {
SpanAttributes.DB_SYSTEM: "elasticsearch", SpanAttributes.DB_SYSTEM: "elasticsearch",
@ -110,7 +114,6 @@ class TestElasticsearchIntegration(TestBase):
span = spans_list[0] span = spans_list[0]
# Check version and name in span's instrumentation info # Check version and name in span's instrumentation info
# self.assertEqualSpanInstrumentationInfo(span, opentelemetry.instrumentation.elasticsearch)
self.assertEqualSpanInstrumentationInfo( self.assertEqualSpanInstrumentationInfo(
span, opentelemetry.instrumentation.elasticsearch span, opentelemetry.instrumentation.elasticsearch
) )
@ -475,6 +478,7 @@ class TestElasticsearchIntegration(TestBase):
"headers": { "headers": {
"accept": "application/vnd.elasticsearch+json; compatible-with=8" "accept": "application/vnd.elasticsearch+json; compatible-with=8"
}, },
"otel_span": None,
} }
elif major_version == 7: elif major_version == 7:
expected_kwargs = { expected_kwargs = {
@ -607,3 +611,30 @@ class TestElasticsearchIntegration(TestBase):
self.assertEqualSpanInstrumentationInfo( self.assertEqualSpanInstrumentationInfo(
span, opentelemetry.instrumentation.elasticsearch span, opentelemetry.instrumentation.elasticsearch
) )
@mark.skipif(
(major_version, minor_version) < (8, 13),
reason="Native OTel since elasticsearch 8.13",
)
@mock.patch.dict(
os.environ,
{"OTEL_PYTHON_INSTRUMENTATION_ELASTICSEARCH_ENABLED": "true"},
)
def test_instrumentation_is_disabled_if_native_support_enabled(
self, request_mock
):
request_mock.return_value = helpers.mock_response("{}")
es = get_elasticsearch_client(hosts=["http://localhost:9200"])
es.index(
index="sw",
id=1,
**normalize_arguments(body={"name": "adam"}, doc_type="_doc"),
)
spans_list = self.get_finished_spans()
self.assertEqual(len(spans_list), 1)
span = spans_list[0]
# Check that name in span's instrumentation info is not from this instrumentation
self.assertEqual(span.instrumentation_info.name, "elasticsearch-api")

View File

@ -92,7 +92,7 @@ envlist =
; below mean these dependencies are being used: ; below mean these dependencies are being used:
; 0: elasticsearch-dsl==6.4.0 elasticsearch==6.8.2 ; 0: elasticsearch-dsl==6.4.0 elasticsearch==6.8.2
; 1: elasticsearch-dsl==7.4.1 elasticsearch==7.17.9 ; 1: elasticsearch-dsl==7.4.1 elasticsearch==7.17.9
; 2: elasticsearch-dsl>=8.0,<8.13 elasticsearch>=8.0,<8.13 ; 2: elasticsearch-dsl==8.13.1 elasticsearch==8.13.1
py3{8,9,10,11}-test-instrumentation-elasticsearch-{0,1,2} py3{8,9,10,11}-test-instrumentation-elasticsearch-{0,1,2}
pypy3-test-instrumentation-elasticsearch-{0,1,2} pypy3-test-instrumentation-elasticsearch-{0,1,2}
lint-instrumentation-elasticsearch lint-instrumentation-elasticsearch