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