diff --git a/CHANGELOG.md b/CHANGELOG.md index 3232e6fef8..a75e1537cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) - `opentelemetry-instrumentation-elasticsearch` Improved support for version 8 ([#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 ([#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)) + ## Version 1.24.0/0.45b0 (2024-03-28) ### Added diff --git a/instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/__init__.py b/instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/__init__.py index acf4596fb0..f8d7920e20 100644 --- a/instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/__init__.py @@ -16,6 +16,15 @@ This library allows tracing HTTP elasticsearch made by the `elasticsearch `_ library. +.. warning:: + The elasticsearch package got native OpenTelemetry support since version + `8.13 `_. + 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 `_. + Usage ----- @@ -54,7 +63,7 @@ def response_hook(span: Span, response: dict) for example: -.. code: python +.. code-block: python from opentelemetry.instrumentation.elasticsearch import ElasticsearchInstrumentor import elasticsearch @@ -81,6 +90,7 @@ def response_hook(span, response): """ import re +import warnings from logging import getLogger from os import environ from typing import Collection @@ -197,6 +207,16 @@ def _wrap_perform_request( ): # pylint: disable=R0912,R0914 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 try: method, url, *_ = args @@ -249,6 +269,11 @@ def normalize_kwargs(k, v): v = str(v) elif isinstance(v, elastic_transport.HttpHeaders): v = dict(v) + elif isinstance( + v, elastic_transport.OpenTelemetrySpan + ): + # the transport Span is always a dummy one + v = None return (k, v) hook_kwargs = dict( diff --git a/instrumentation/opentelemetry-instrumentation-elasticsearch/test-requirements-2.txt b/instrumentation/opentelemetry-instrumentation-elasticsearch/test-requirements-2.txt index 23d87f93dd..6b0d677ec7 100644 --- a/instrumentation/opentelemetry-instrumentation-elasticsearch/test-requirements-2.txt +++ b/instrumentation/opentelemetry-instrumentation-elasticsearch/test-requirements-2.txt @@ -1,9 +1,9 @@ asgiref==3.7.2 attrs==23.2.0 Deprecated==1.2.14 -elasticsearch==8.12.1 -elasticsearch-dsl==8.12.0 -elastic-transport==8.12.0 +elasticsearch==8.13.1 +elasticsearch-dsl==8.13.1 +elastic-transport==8.13.0 importlib-metadata==6.11.0 iniconfig==2.0.0 packaging==23.2 diff --git a/instrumentation/opentelemetry-instrumentation-elasticsearch/tests/test_elasticsearch.py b/instrumentation/opentelemetry-instrumentation-elasticsearch/tests/test_elasticsearch.py index b0ee170329..b7e24d87c9 100644 --- a/instrumentation/opentelemetry-instrumentation-elasticsearch/tests/test_elasticsearch.py +++ b/instrumentation/opentelemetry-instrumentation-elasticsearch/tests/test_elasticsearch.py @@ -23,6 +23,7 @@ import elasticsearch.exceptions from elasticsearch import Elasticsearch from elasticsearch_dsl import Search +from pytest import mark import opentelemetry.instrumentation.elasticsearch from opentelemetry import trace @@ -36,7 +37,7 @@ 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: 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.dict( + os.environ, {"OTEL_PYTHON_INSTRUMENTATION_ELASTICSEARCH_ENABLED": "false"} +) class TestElasticsearchIntegration(TestBase): search_attributes = { SpanAttributes.DB_SYSTEM: "elasticsearch", @@ -110,7 +114,6 @@ def test_instrumentor(self, request_mock): span = spans_list[0] # Check version and name in span's instrumentation info - # self.assertEqualSpanInstrumentationInfo(span, opentelemetry.instrumentation.elasticsearch) self.assertEqualSpanInstrumentationInfo( span, opentelemetry.instrumentation.elasticsearch ) @@ -475,6 +478,7 @@ def request_hook(span, method, url, kwargs): "headers": { "accept": "application/vnd.elasticsearch+json; compatible-with=8" }, + "otel_span": None, } elif major_version == 7: expected_kwargs = { @@ -607,3 +611,30 @@ def test_bulk(self, request_mock): self.assertEqualSpanInstrumentationInfo( 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") diff --git a/tox.ini b/tox.ini index d6fb9eee10..a014306640 100644 --- a/tox.ini +++ b/tox.ini @@ -92,7 +92,7 @@ envlist = ; below mean these dependencies are being used: ; 0: elasticsearch-dsl==6.4.0 elasticsearch==6.8.2 ; 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} pypy3-test-instrumentation-elasticsearch-{0,1,2} lint-instrumentation-elasticsearch