We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Handle elasticsearch client native instrumentation
**Describe your environment**

Elasticsearch client added native opentelemetry instrumentation in [8.13.0](https://www.elastic.co/guide/en/elasticsearch/client/python-api/master/release-notes.html#rn-8-13-0) and so currently without manual intervention it is possible to have our instrumentation and the client one create spans concurrently.

**Steps to reproduce**

Add opentelemetry instrumentation using elasticsearch client 8.13.0

**What is the expected behavior?**

Only the elasticsearch client creates spans.

**What is the actual behavior?**

Both client and opentelemetry instrumentation creates their own spans.

**Additional context**

Java opentelemetry instrumentation disables its instrumentation if a new enough elasticsearch client is found https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/9337/
Handle elasticsearch client native instrumentation
**Describe your environment**

Elasticsearch client added native opentelemetry instrumentation in [8.13.0](https://www.elastic.co/guide/en/elasticsearch/client/python-api/master/release-notes.html#rn-8-13-0) and so currently without manual intervention it is possible to have our instrumentation and the client one create spans concurrently.

**Steps to reproduce**

Add opentelemetry instrumentation using elasticsearch client 8.13.0

**What is the expected behavior?**

Only the elasticsearch client creates spans.

**What is the actual behavior?**

Both client and opentelemetry instrumentation creates their own spans.

**Additional context**

Java opentelemetry instrumentation disables its instrumentation if a new enough elasticsearch client is found https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/9337/

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/__init__.py`
Content:
```
1 # Copyright The OpenTelemetry Authors
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 """
16 This library allows tracing HTTP elasticsearch made by the
17 `elasticsearch <https://elasticsearch-py.readthedocs.io/en/master/>`_ library.
18 
19 Usage
20 -----
21 
22 .. code-block:: python
23 
24     from opentelemetry.instrumentation.elasticsearch import ElasticsearchInstrumentor
25     import elasticsearch
26 
27 
28     # instrument elasticsearch
29     ElasticsearchInstrumentor().instrument()
30 
31     # Using elasticsearch as normal now will automatically generate spans
32     es = elasticsearch.Elasticsearch()
33     es.index(index='my-index', doc_type='my-type', id=1, body={'my': 'data', 'timestamp': datetime.now()})
34     es.get(index='my-index', doc_type='my-type', id=1)
35 
36 Elasticsearch instrumentation prefixes operation names with the string "Elasticsearch". This
37 can be changed to a different string by either setting the OTEL_PYTHON_ELASTICSEARCH_NAME_PREFIX
38 environment variable or by passing the prefix as an argument to the instrumentor. For example,
39 
40 
41 .. code-block:: python
42 
43     ElasticsearchInstrumentor("my-custom-prefix").instrument()
44 
45 The instrument() method accepts the following keyword args:
46 tracer_provider (TracerProvider) - an optional tracer provider
47 request_hook (Callable) - a function with extra user-defined logic to be performed before performing the request
48 this function signature is:
49 def request_hook(span: Span, method: str, url: str, kwargs)
50 
51 response_hook (Callable) - a function with extra user-defined logic to be performed after performing the request
52 this function signature is:
53 def response_hook(span: Span, response: dict)
54 
55 for example:
56 
57 .. code: python
58 
59     from opentelemetry.instrumentation.elasticsearch import ElasticsearchInstrumentor
60     import elasticsearch
61 
62     def request_hook(span, method, url, kwargs):
63         if span and span.is_recording():
64             span.set_attribute("custom_user_attribute_from_request_hook", "some-value")
65 
66     def response_hook(span, response):
67         if span and span.is_recording():
68             span.set_attribute("custom_user_attribute_from_response_hook", "some-value")
69 
70     # instrument elasticsearch with request and response hooks
71     ElasticsearchInstrumentor().instrument(request_hook=request_hook, response_hook=response_hook)
72 
73     # Using elasticsearch as normal now will automatically generate spans,
74     # including user custom attributes added from the hooks
75     es = elasticsearch.Elasticsearch()
76     es.index(index='my-index', doc_type='my-type', id=1, body={'my': 'data', 'timestamp': datetime.now()})
77     es.get(index='my-index', doc_type='my-type', id=1)
78 
79 API
80 ---
81 """
82 
83 import re
84 from logging import getLogger
85 from os import environ
86 from typing import Collection
87 
88 import elasticsearch
89 import elasticsearch.exceptions
90 from wrapt import wrap_function_wrapper as _wrap
91 
92 from opentelemetry.instrumentation.elasticsearch.package import _instruments
93 from opentelemetry.instrumentation.elasticsearch.version import __version__
94 from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
95 from opentelemetry.instrumentation.utils import unwrap
96 from opentelemetry.semconv.trace import SpanAttributes
97 from opentelemetry.trace import SpanKind, Status, StatusCode, get_tracer
98 
99 from .utils import sanitize_body
100 
101 # Split of elasticsearch and elastic_transport in 8.0.0+
102 # https://www.elastic.co/guide/en/elasticsearch/client/python-api/master/release-notes.html#rn-8-0-0
103 es_transport_split = elasticsearch.VERSION[0] > 7
104 if es_transport_split:
105     import elastic_transport
106     from elastic_transport._models import DefaultType
107 
108 logger = getLogger(__name__)
109 
110 
111 # Values to add as tags from the actual
112 # payload returned by Elasticsearch, if any.
113 _ATTRIBUTES_FROM_RESULT = [
114     "found",
115     "timed_out",
116     "took",
117 ]
118 
119 _DEFAULT_OP_NAME = "request"
120 
121 
122 class ElasticsearchInstrumentor(BaseInstrumentor):
123     """An instrumentor for elasticsearch
124     See `BaseInstrumentor`
125     """
126 
127     def __init__(self, span_name_prefix=None):
128         if not span_name_prefix:
129             span_name_prefix = environ.get(
130                 "OTEL_PYTHON_ELASTICSEARCH_NAME_PREFIX",
131                 "Elasticsearch",
132             )
133         self._span_name_prefix = span_name_prefix.strip()
134         super().__init__()
135 
136     def instrumentation_dependencies(self) -> Collection[str]:
137         return _instruments
138 
139     def _instrument(self, **kwargs):
140         """
141         Instruments Elasticsearch module
142         """
143         tracer_provider = kwargs.get("tracer_provider")
144         tracer = get_tracer(
145             __name__,
146             __version__,
147             tracer_provider,
148             schema_url="https://opentelemetry.io/schemas/1.11.0",
149         )
150         request_hook = kwargs.get("request_hook")
151         response_hook = kwargs.get("response_hook")
152         if es_transport_split:
153             _wrap(
154                 elastic_transport,
155                 "Transport.perform_request",
156                 _wrap_perform_request(
157                     tracer,
158                     self._span_name_prefix,
159                     request_hook,
160                     response_hook,
161                 ),
162             )
163         else:
164             _wrap(
165                 elasticsearch,
166                 "Transport.perform_request",
167                 _wrap_perform_request(
168                     tracer,
169                     self._span_name_prefix,
170                     request_hook,
171                     response_hook,
172                 ),
173             )
174 
175     def _uninstrument(self, **kwargs):
176         # pylint: disable=no-member
177         transport_class = (
178             elastic_transport.Transport
179             if es_transport_split
180             else elasticsearch.Transport
181         )
182         unwrap(transport_class, "perform_request")
183 
184 
185 _regex_doc_url = re.compile(r"/_doc/([^/]+)")
186 
187 # search api https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html
188 _regex_search_url = re.compile(r"/([^/]+)/_search[/]?")
189 
190 
191 # pylint: disable=too-many-statements
192 def _wrap_perform_request(
193     tracer,
194     span_name_prefix,
195     request_hook=None,
196     response_hook=None,
197 ):
198     # pylint: disable=R0912,R0914
199     def wrapper(wrapped, _, args, kwargs):
200         method = url = None
201         try:
202             method, url, *_ = args
203         except IndexError:
204             logger.warning(
205                 "expected perform_request to receive two positional arguments. "
206                 "Got %d",
207                 len(args),
208             )
209 
210         op_name = span_name_prefix + (url or method or _DEFAULT_OP_NAME)
211 
212         doc_id = None
213         search_target = None
214 
215         if url:
216             # TODO: This regex-based solution avoids creating an unbounded number of span names, but should be replaced by instrumenting individual Elasticsearch methods instead of Transport.perform_request()
217             # A limitation of the regex is that only the '_doc' mapping type is supported. Mapping types are deprecated since Elasticsearch 7
218             # https://github.com/open-telemetry/opentelemetry-python-contrib/issues/708
219             match = _regex_doc_url.search(url)
220             if match is not None:
221                 # Remove the full document ID from the URL
222                 doc_span = match.span()
223                 op_name = (
224                     span_name_prefix
225                     + url[: doc_span[0]]
226                     + "/_doc/:id"
227                     + url[doc_span[1] :]
228                 )
229                 # Put the document ID in attributes
230                 doc_id = match.group(1)
231             match = _regex_search_url.search(url)
232             if match is not None:
233                 op_name = span_name_prefix + "/<target>/_search"
234                 search_target = match.group(1)
235 
236         params = kwargs.get("params", {})
237         body = kwargs.get("body", None)
238 
239         with tracer.start_as_current_span(
240             op_name,
241             kind=SpanKind.CLIENT,
242         ) as span:
243             if callable(request_hook):
244                 # elasticsearch 8 changed the parameters quite a bit
245                 if es_transport_split:
246 
247                     def normalize_kwargs(k, v):
248                         if isinstance(v, DefaultType):
249                             v = str(v)
250                         elif isinstance(v, elastic_transport.HttpHeaders):
251                             v = dict(v)
252                         return (k, v)
253 
254                     hook_kwargs = dict(
255                         normalize_kwargs(k, v) for k, v in kwargs.items()
256                     )
257                 else:
258                     hook_kwargs = kwargs
259                 request_hook(span, method, url, hook_kwargs)
260 
261             if span.is_recording():
262                 attributes = {
263                     SpanAttributes.DB_SYSTEM: "elasticsearch",
264                 }
265                 if url:
266                     attributes["elasticsearch.url"] = url
267                 if method:
268                     attributes["elasticsearch.method"] = method
269                 if body:
270                     # Don't set db.statement for bulk requests, as it can be very large
271                     if isinstance(body, dict):
272                         attributes[SpanAttributes.DB_STATEMENT] = (
273                             sanitize_body(body)
274                         )
275                 if params:
276                     attributes["elasticsearch.params"] = str(params)
277                 if doc_id:
278                     attributes["elasticsearch.id"] = doc_id
279                 if search_target:
280                     attributes["elasticsearch.target"] = search_target
281                 for key, value in attributes.items():
282                     span.set_attribute(key, value)
283 
284             rv = wrapped(*args, **kwargs)
285 
286             body = rv.body if es_transport_split else rv
287             if isinstance(body, dict) and span.is_recording():
288                 for member in _ATTRIBUTES_FROM_RESULT:
289                     if member in body:
290                         span.set_attribute(
291                             f"elasticsearch.{member}",
292                             str(body[member]),
293                         )
294 
295             # since the transport split the raising of exceptions that set the error status
296             # are called after this code so need to set error status manually
297             if es_transport_split and span.is_recording():
298                 if not (method == "HEAD" and rv.meta.status == 404) and (
299                     not 200 <= rv.meta.status < 299
300                 ):
301                     exception = elasticsearch.exceptions.HTTP_EXCEPTIONS.get(
302                         rv.meta.status, elasticsearch.exceptions.ApiError
303                     )
304                     message = str(body)
305                     if isinstance(body, dict):
306                         error = body.get("error", message)
307                         if isinstance(error, dict) and "type" in error:
308                             error = error["type"]
309                         message = error
310 
311                     span.set_status(
312                         Status(
313                             status_code=StatusCode.ERROR,
314                             description=f"{exception.__name__}: {message}",
315                         )
316                     )
317 
318             if callable(response_hook):
319                 response_hook(span, body)
320             return rv
321 
322     return wrapper
323
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/__init__.py b/instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/__init__.py
--- 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 <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
 -----
 
@@ -54,7 +63,7 @@
 
 for example:
 
-.. code: python
+.. code-block: python
 
     from opentelemetry.instrumentation.elasticsearch import ElasticsearchInstrumentor
     import elasticsearch
@@ -81,6 +90,7 @@
 """
 
 import re
+import warnings
 from logging import getLogger
 from os import environ
 from typing import Collection
@@ -197,6 +207,16 @@
 ):
     # 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 @@
                             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(

{"golden_diff": "diff --git a/instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/__init__.py b/instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/__init__.py\n--- a/instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/__init__.py\n+++ b/instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/__init__.py\n@@ -16,6 +16,15 @@\n This library allows tracing HTTP elasticsearch made by the\n `elasticsearch <https://elasticsearch-py.readthedocs.io/en/master/>`_ library.\n \n+.. warning::\n+    The elasticsearch package got native OpenTelemetry support since version\n+    `8.13 <https://www.elastic.co/guide/en/elasticsearch/client/python-api/current/release-notes.html#rn-8-13-0>`_.\n+    To avoid duplicated tracing this instrumentation disables itself if it finds an elasticsearch client\n+    that has OpenTelemetry support enabled.\n+\n+    Please be aware that the two libraries may use a different semantic convention, see\n+    `elasticsearch documentation <https://www.elastic.co/guide/en/elasticsearch/client/python-api/current/opentelemetry.html>`_.\n+\n Usage\n -----\n \n@@ -54,7 +63,7 @@\n \n for example:\n \n-.. code: python\n+.. code-block: python\n \n     from opentelemetry.instrumentation.elasticsearch import ElasticsearchInstrumentor\n     import elasticsearch\n@@ -81,6 +90,7 @@\n \"\"\"\n \n import re\n+import warnings\n from logging import getLogger\n from os import environ\n from typing import Collection\n@@ -197,6 +207,16 @@\n ):\n     # pylint: disable=R0912,R0914\n     def wrapper(wrapped, _, args, kwargs):\n+        # if wrapped elasticsearch has native OTel instrumentation just call the wrapped function\n+        otel_span = kwargs.get(\"otel_span\")\n+        if otel_span and otel_span.otel_span:\n+            warnings.warn(\n+                \"Instrumentation disabled, relying on elasticsearch native OTel support, see \"\n+                \"https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/elasticsearch/elasticsearch.html\",\n+                Warning,\n+            )\n+            return wrapped(*args, **kwargs)\n+\n         method = url = None\n         try:\n             method, url, *_ = args\n@@ -249,6 +269,11 @@\n                             v = str(v)\n                         elif isinstance(v, elastic_transport.HttpHeaders):\n                             v = dict(v)\n+                        elif isinstance(\n+                            v, elastic_transport.OpenTelemetrySpan\n+                        ):\n+                            # the transport Span is always a dummy one\n+                            v = None\n                         return (k, v)\n \n                     hook_kwargs = dict(\n", "issue": "Handle elasticsearch client native instrumentation\n**Describe your environment**\r\n\r\nElasticsearch client added native opentelemetry instrumentation in [8.13.0](https://www.elastic.co/guide/en/elasticsearch/client/python-api/master/release-notes.html#rn-8-13-0) and so currently without manual intervention it is possible to have our instrumentation and the client one create spans concurrently.\r\n\r\n**Steps to reproduce**\r\n\r\nAdd opentelemetry instrumentation using elasticsearch client 8.13.0\r\n\r\n**What is the expected behavior?**\r\n\r\nOnly the elasticsearch client creates spans.\r\n\r\n**What is the actual behavior?**\r\n\r\nBoth client and opentelemetry instrumentation creates their own spans.\r\n\r\n**Additional context**\r\n\r\nJava opentelemetry instrumentation disables its instrumentation if a new enough elasticsearch client is found https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/9337/\nHandle elasticsearch client native instrumentation\n**Describe your environment**\r\n\r\nElasticsearch client added native opentelemetry instrumentation in [8.13.0](https://www.elastic.co/guide/en/elasticsearch/client/python-api/master/release-notes.html#rn-8-13-0) and so currently without manual intervention it is possible to have our instrumentation and the client one create spans concurrently.\r\n\r\n**Steps to reproduce**\r\n\r\nAdd opentelemetry instrumentation using elasticsearch client 8.13.0\r\n\r\n**What is the expected behavior?**\r\n\r\nOnly the elasticsearch client creates spans.\r\n\r\n**What is the actual behavior?**\r\n\r\nBoth client and opentelemetry instrumentation creates their own spans.\r\n\r\n**Additional context**\r\n\r\nJava opentelemetry instrumentation disables its instrumentation if a new enough elasticsearch client is found https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/9337/\n", "before_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"\nThis library allows tracing HTTP elasticsearch made by the\n`elasticsearch <https://elasticsearch-py.readthedocs.io/en/master/>`_ library.\n\nUsage\n-----\n\n.. code-block:: python\n\n    from opentelemetry.instrumentation.elasticsearch import ElasticsearchInstrumentor\n    import elasticsearch\n\n\n    # instrument elasticsearch\n    ElasticsearchInstrumentor().instrument()\n\n    # Using elasticsearch as normal now will automatically generate spans\n    es = elasticsearch.Elasticsearch()\n    es.index(index='my-index', doc_type='my-type', id=1, body={'my': 'data', 'timestamp': datetime.now()})\n    es.get(index='my-index', doc_type='my-type', id=1)\n\nElasticsearch instrumentation prefixes operation names with the string \"Elasticsearch\". This\ncan be changed to a different string by either setting the OTEL_PYTHON_ELASTICSEARCH_NAME_PREFIX\nenvironment variable or by passing the prefix as an argument to the instrumentor. For example,\n\n\n.. code-block:: python\n\n    ElasticsearchInstrumentor(\"my-custom-prefix\").instrument()\n\nThe instrument() method accepts the following keyword args:\ntracer_provider (TracerProvider) - an optional tracer provider\nrequest_hook (Callable) - a function with extra user-defined logic to be performed before performing the request\nthis function signature is:\ndef request_hook(span: Span, method: str, url: str, kwargs)\n\nresponse_hook (Callable) - a function with extra user-defined logic to be performed after performing the request\nthis function signature is:\ndef response_hook(span: Span, response: dict)\n\nfor example:\n\n.. code: python\n\n    from opentelemetry.instrumentation.elasticsearch import ElasticsearchInstrumentor\n    import elasticsearch\n\n    def request_hook(span, method, url, kwargs):\n        if span and span.is_recording():\n            span.set_attribute(\"custom_user_attribute_from_request_hook\", \"some-value\")\n\n    def response_hook(span, response):\n        if span and span.is_recording():\n            span.set_attribute(\"custom_user_attribute_from_response_hook\", \"some-value\")\n\n    # instrument elasticsearch with request and response hooks\n    ElasticsearchInstrumentor().instrument(request_hook=request_hook, response_hook=response_hook)\n\n    # Using elasticsearch as normal now will automatically generate spans,\n    # including user custom attributes added from the hooks\n    es = elasticsearch.Elasticsearch()\n    es.index(index='my-index', doc_type='my-type', id=1, body={'my': 'data', 'timestamp': datetime.now()})\n    es.get(index='my-index', doc_type='my-type', id=1)\n\nAPI\n---\n\"\"\"\n\nimport re\nfrom logging import getLogger\nfrom os import environ\nfrom typing import Collection\n\nimport elasticsearch\nimport elasticsearch.exceptions\nfrom wrapt import wrap_function_wrapper as _wrap\n\nfrom opentelemetry.instrumentation.elasticsearch.package import _instruments\nfrom opentelemetry.instrumentation.elasticsearch.version import __version__\nfrom opentelemetry.instrumentation.instrumentor import BaseInstrumentor\nfrom opentelemetry.instrumentation.utils import unwrap\nfrom opentelemetry.semconv.trace import SpanAttributes\nfrom opentelemetry.trace import SpanKind, Status, StatusCode, get_tracer\n\nfrom .utils import sanitize_body\n\n# Split of elasticsearch and elastic_transport in 8.0.0+\n# https://www.elastic.co/guide/en/elasticsearch/client/python-api/master/release-notes.html#rn-8-0-0\nes_transport_split = elasticsearch.VERSION[0] > 7\nif es_transport_split:\n    import elastic_transport\n    from elastic_transport._models import DefaultType\n\nlogger = getLogger(__name__)\n\n\n# Values to add as tags from the actual\n# payload returned by Elasticsearch, if any.\n_ATTRIBUTES_FROM_RESULT = [\n    \"found\",\n    \"timed_out\",\n    \"took\",\n]\n\n_DEFAULT_OP_NAME = \"request\"\n\n\nclass ElasticsearchInstrumentor(BaseInstrumentor):\n    \"\"\"An instrumentor for elasticsearch\n    See `BaseInstrumentor`\n    \"\"\"\n\n    def __init__(self, span_name_prefix=None):\n        if not span_name_prefix:\n            span_name_prefix = environ.get(\n                \"OTEL_PYTHON_ELASTICSEARCH_NAME_PREFIX\",\n                \"Elasticsearch\",\n            )\n        self._span_name_prefix = span_name_prefix.strip()\n        super().__init__()\n\n    def instrumentation_dependencies(self) -> Collection[str]:\n        return _instruments\n\n    def _instrument(self, **kwargs):\n        \"\"\"\n        Instruments Elasticsearch module\n        \"\"\"\n        tracer_provider = kwargs.get(\"tracer_provider\")\n        tracer = get_tracer(\n            __name__,\n            __version__,\n            tracer_provider,\n            schema_url=\"https://opentelemetry.io/schemas/1.11.0\",\n        )\n        request_hook = kwargs.get(\"request_hook\")\n        response_hook = kwargs.get(\"response_hook\")\n        if es_transport_split:\n            _wrap(\n                elastic_transport,\n                \"Transport.perform_request\",\n                _wrap_perform_request(\n                    tracer,\n                    self._span_name_prefix,\n                    request_hook,\n                    response_hook,\n                ),\n            )\n        else:\n            _wrap(\n                elasticsearch,\n                \"Transport.perform_request\",\n                _wrap_perform_request(\n                    tracer,\n                    self._span_name_prefix,\n                    request_hook,\n                    response_hook,\n                ),\n            )\n\n    def _uninstrument(self, **kwargs):\n        # pylint: disable=no-member\n        transport_class = (\n            elastic_transport.Transport\n            if es_transport_split\n            else elasticsearch.Transport\n        )\n        unwrap(transport_class, \"perform_request\")\n\n\n_regex_doc_url = re.compile(r\"/_doc/([^/]+)\")\n\n# search api https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html\n_regex_search_url = re.compile(r\"/([^/]+)/_search[/]?\")\n\n\n# pylint: disable=too-many-statements\ndef _wrap_perform_request(\n    tracer,\n    span_name_prefix,\n    request_hook=None,\n    response_hook=None,\n):\n    # pylint: disable=R0912,R0914\n    def wrapper(wrapped, _, args, kwargs):\n        method = url = None\n        try:\n            method, url, *_ = args\n        except IndexError:\n            logger.warning(\n                \"expected perform_request to receive two positional arguments. \"\n                \"Got %d\",\n                len(args),\n            )\n\n        op_name = span_name_prefix + (url or method or _DEFAULT_OP_NAME)\n\n        doc_id = None\n        search_target = None\n\n        if url:\n            # TODO: This regex-based solution avoids creating an unbounded number of span names, but should be replaced by instrumenting individual Elasticsearch methods instead of Transport.perform_request()\n            # A limitation of the regex is that only the '_doc' mapping type is supported. Mapping types are deprecated since Elasticsearch 7\n            # https://github.com/open-telemetry/opentelemetry-python-contrib/issues/708\n            match = _regex_doc_url.search(url)\n            if match is not None:\n                # Remove the full document ID from the URL\n                doc_span = match.span()\n                op_name = (\n                    span_name_prefix\n                    + url[: doc_span[0]]\n                    + \"/_doc/:id\"\n                    + url[doc_span[1] :]\n                )\n                # Put the document ID in attributes\n                doc_id = match.group(1)\n            match = _regex_search_url.search(url)\n            if match is not None:\n                op_name = span_name_prefix + \"/<target>/_search\"\n                search_target = match.group(1)\n\n        params = kwargs.get(\"params\", {})\n        body = kwargs.get(\"body\", None)\n\n        with tracer.start_as_current_span(\n            op_name,\n            kind=SpanKind.CLIENT,\n        ) as span:\n            if callable(request_hook):\n                # elasticsearch 8 changed the parameters quite a bit\n                if es_transport_split:\n\n                    def normalize_kwargs(k, v):\n                        if isinstance(v, DefaultType):\n                            v = str(v)\n                        elif isinstance(v, elastic_transport.HttpHeaders):\n                            v = dict(v)\n                        return (k, v)\n\n                    hook_kwargs = dict(\n                        normalize_kwargs(k, v) for k, v in kwargs.items()\n                    )\n                else:\n                    hook_kwargs = kwargs\n                request_hook(span, method, url, hook_kwargs)\n\n            if span.is_recording():\n                attributes = {\n                    SpanAttributes.DB_SYSTEM: \"elasticsearch\",\n                }\n                if url:\n                    attributes[\"elasticsearch.url\"] = url\n                if method:\n                    attributes[\"elasticsearch.method\"] = method\n                if body:\n                    # Don't set db.statement for bulk requests, as it can be very large\n                    if isinstance(body, dict):\n                        attributes[SpanAttributes.DB_STATEMENT] = (\n                            sanitize_body(body)\n                        )\n                if params:\n                    attributes[\"elasticsearch.params\"] = str(params)\n                if doc_id:\n                    attributes[\"elasticsearch.id\"] = doc_id\n                if search_target:\n                    attributes[\"elasticsearch.target\"] = search_target\n                for key, value in attributes.items():\n                    span.set_attribute(key, value)\n\n            rv = wrapped(*args, **kwargs)\n\n            body = rv.body if es_transport_split else rv\n            if isinstance(body, dict) and span.is_recording():\n                for member in _ATTRIBUTES_FROM_RESULT:\n                    if member in body:\n                        span.set_attribute(\n                            f\"elasticsearch.{member}\",\n                            str(body[member]),\n                        )\n\n            # since the transport split the raising of exceptions that set the error status\n            # are called after this code so need to set error status manually\n            if es_transport_split and span.is_recording():\n                if not (method == \"HEAD\" and rv.meta.status == 404) and (\n                    not 200 <= rv.meta.status < 299\n                ):\n                    exception = elasticsearch.exceptions.HTTP_EXCEPTIONS.get(\n                        rv.meta.status, elasticsearch.exceptions.ApiError\n                    )\n                    message = str(body)\n                    if isinstance(body, dict):\n                        error = body.get(\"error\", message)\n                        if isinstance(error, dict) and \"type\" in error:\n                            error = error[\"type\"]\n                        message = error\n\n                    span.set_status(\n                        Status(\n                            status_code=StatusCode.ERROR,\n                            description=f\"{exception.__name__}: {message}\",\n                        )\n                    )\n\n            if callable(response_hook):\n                response_hook(span, body)\n            return rv\n\n    return wrapper\n", "path": "instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/__init__.py"}], "after_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"\nThis library allows tracing HTTP elasticsearch made by the\n`elasticsearch <https://elasticsearch-py.readthedocs.io/en/master/>`_ library.\n\n.. warning::\n    The elasticsearch package got native OpenTelemetry support since version\n    `8.13 <https://www.elastic.co/guide/en/elasticsearch/client/python-api/current/release-notes.html#rn-8-13-0>`_.\n    To avoid duplicated tracing this instrumentation disables itself if it finds an elasticsearch client\n    that has OpenTelemetry support enabled.\n\n    Please be aware that the two libraries may use a different semantic convention, see\n    `elasticsearch documentation <https://www.elastic.co/guide/en/elasticsearch/client/python-api/current/opentelemetry.html>`_.\n\nUsage\n-----\n\n.. code-block:: python\n\n    from opentelemetry.instrumentation.elasticsearch import ElasticsearchInstrumentor\n    import elasticsearch\n\n\n    # instrument elasticsearch\n    ElasticsearchInstrumentor().instrument()\n\n    # Using elasticsearch as normal now will automatically generate spans\n    es = elasticsearch.Elasticsearch()\n    es.index(index='my-index', doc_type='my-type', id=1, body={'my': 'data', 'timestamp': datetime.now()})\n    es.get(index='my-index', doc_type='my-type', id=1)\n\nElasticsearch instrumentation prefixes operation names with the string \"Elasticsearch\". This\ncan be changed to a different string by either setting the OTEL_PYTHON_ELASTICSEARCH_NAME_PREFIX\nenvironment variable or by passing the prefix as an argument to the instrumentor. For example,\n\n\n.. code-block:: python\n\n    ElasticsearchInstrumentor(\"my-custom-prefix\").instrument()\n\nThe instrument() method accepts the following keyword args:\ntracer_provider (TracerProvider) - an optional tracer provider\nrequest_hook (Callable) - a function with extra user-defined logic to be performed before performing the request\nthis function signature is:\ndef request_hook(span: Span, method: str, url: str, kwargs)\n\nresponse_hook (Callable) - a function with extra user-defined logic to be performed after performing the request\nthis function signature is:\ndef response_hook(span: Span, response: dict)\n\nfor example:\n\n.. code-block: python\n\n    from opentelemetry.instrumentation.elasticsearch import ElasticsearchInstrumentor\n    import elasticsearch\n\n    def request_hook(span, method, url, kwargs):\n        if span and span.is_recording():\n            span.set_attribute(\"custom_user_attribute_from_request_hook\", \"some-value\")\n\n    def response_hook(span, response):\n        if span and span.is_recording():\n            span.set_attribute(\"custom_user_attribute_from_response_hook\", \"some-value\")\n\n    # instrument elasticsearch with request and response hooks\n    ElasticsearchInstrumentor().instrument(request_hook=request_hook, response_hook=response_hook)\n\n    # Using elasticsearch as normal now will automatically generate spans,\n    # including user custom attributes added from the hooks\n    es = elasticsearch.Elasticsearch()\n    es.index(index='my-index', doc_type='my-type', id=1, body={'my': 'data', 'timestamp': datetime.now()})\n    es.get(index='my-index', doc_type='my-type', id=1)\n\nAPI\n---\n\"\"\"\n\nimport re\nimport warnings\nfrom logging import getLogger\nfrom os import environ\nfrom typing import Collection\n\nimport elasticsearch\nimport elasticsearch.exceptions\nfrom wrapt import wrap_function_wrapper as _wrap\n\nfrom opentelemetry.instrumentation.elasticsearch.package import _instruments\nfrom opentelemetry.instrumentation.elasticsearch.version import __version__\nfrom opentelemetry.instrumentation.instrumentor import BaseInstrumentor\nfrom opentelemetry.instrumentation.utils import unwrap\nfrom opentelemetry.semconv.trace import SpanAttributes\nfrom opentelemetry.trace import SpanKind, Status, StatusCode, get_tracer\n\nfrom .utils import sanitize_body\n\n# Split of elasticsearch and elastic_transport in 8.0.0+\n# https://www.elastic.co/guide/en/elasticsearch/client/python-api/master/release-notes.html#rn-8-0-0\nes_transport_split = elasticsearch.VERSION[0] > 7\nif es_transport_split:\n    import elastic_transport\n    from elastic_transport._models import DefaultType\n\nlogger = getLogger(__name__)\n\n\n# Values to add as tags from the actual\n# payload returned by Elasticsearch, if any.\n_ATTRIBUTES_FROM_RESULT = [\n    \"found\",\n    \"timed_out\",\n    \"took\",\n]\n\n_DEFAULT_OP_NAME = \"request\"\n\n\nclass ElasticsearchInstrumentor(BaseInstrumentor):\n    \"\"\"An instrumentor for elasticsearch\n    See `BaseInstrumentor`\n    \"\"\"\n\n    def __init__(self, span_name_prefix=None):\n        if not span_name_prefix:\n            span_name_prefix = environ.get(\n                \"OTEL_PYTHON_ELASTICSEARCH_NAME_PREFIX\",\n                \"Elasticsearch\",\n            )\n        self._span_name_prefix = span_name_prefix.strip()\n        super().__init__()\n\n    def instrumentation_dependencies(self) -> Collection[str]:\n        return _instruments\n\n    def _instrument(self, **kwargs):\n        \"\"\"\n        Instruments Elasticsearch module\n        \"\"\"\n        tracer_provider = kwargs.get(\"tracer_provider\")\n        tracer = get_tracer(\n            __name__,\n            __version__,\n            tracer_provider,\n            schema_url=\"https://opentelemetry.io/schemas/1.11.0\",\n        )\n        request_hook = kwargs.get(\"request_hook\")\n        response_hook = kwargs.get(\"response_hook\")\n        if es_transport_split:\n            _wrap(\n                elastic_transport,\n                \"Transport.perform_request\",\n                _wrap_perform_request(\n                    tracer,\n                    self._span_name_prefix,\n                    request_hook,\n                    response_hook,\n                ),\n            )\n        else:\n            _wrap(\n                elasticsearch,\n                \"Transport.perform_request\",\n                _wrap_perform_request(\n                    tracer,\n                    self._span_name_prefix,\n                    request_hook,\n                    response_hook,\n                ),\n            )\n\n    def _uninstrument(self, **kwargs):\n        # pylint: disable=no-member\n        transport_class = (\n            elastic_transport.Transport\n            if es_transport_split\n            else elasticsearch.Transport\n        )\n        unwrap(transport_class, \"perform_request\")\n\n\n_regex_doc_url = re.compile(r\"/_doc/([^/]+)\")\n\n# search api https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html\n_regex_search_url = re.compile(r\"/([^/]+)/_search[/]?\")\n\n\n# pylint: disable=too-many-statements\ndef _wrap_perform_request(\n    tracer,\n    span_name_prefix,\n    request_hook=None,\n    response_hook=None,\n):\n    # pylint: disable=R0912,R0914\n    def wrapper(wrapped, _, args, kwargs):\n        # if wrapped elasticsearch has native OTel instrumentation just call the wrapped function\n        otel_span = kwargs.get(\"otel_span\")\n        if otel_span and otel_span.otel_span:\n            warnings.warn(\n                \"Instrumentation disabled, relying on elasticsearch native OTel support, see \"\n                \"https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/elasticsearch/elasticsearch.html\",\n                Warning,\n            )\n            return wrapped(*args, **kwargs)\n\n        method = url = None\n        try:\n            method, url, *_ = args\n        except IndexError:\n            logger.warning(\n                \"expected perform_request to receive two positional arguments. \"\n                \"Got %d\",\n                len(args),\n            )\n\n        op_name = span_name_prefix + (url or method or _DEFAULT_OP_NAME)\n\n        doc_id = None\n        search_target = None\n\n        if url:\n            # TODO: This regex-based solution avoids creating an unbounded number of span names, but should be replaced by instrumenting individual Elasticsearch methods instead of Transport.perform_request()\n            # A limitation of the regex is that only the '_doc' mapping type is supported. Mapping types are deprecated since Elasticsearch 7\n            # https://github.com/open-telemetry/opentelemetry-python-contrib/issues/708\n            match = _regex_doc_url.search(url)\n            if match is not None:\n                # Remove the full document ID from the URL\n                doc_span = match.span()\n                op_name = (\n                    span_name_prefix\n                    + url[: doc_span[0]]\n                    + \"/_doc/:id\"\n                    + url[doc_span[1] :]\n                )\n                # Put the document ID in attributes\n                doc_id = match.group(1)\n            match = _regex_search_url.search(url)\n            if match is not None:\n                op_name = span_name_prefix + \"/<target>/_search\"\n                search_target = match.group(1)\n\n        params = kwargs.get(\"params\", {})\n        body = kwargs.get(\"body\", None)\n\n        with tracer.start_as_current_span(\n            op_name,\n            kind=SpanKind.CLIENT,\n        ) as span:\n            if callable(request_hook):\n                # elasticsearch 8 changed the parameters quite a bit\n                if es_transport_split:\n\n                    def normalize_kwargs(k, v):\n                        if isinstance(v, DefaultType):\n                            v = str(v)\n                        elif isinstance(v, elastic_transport.HttpHeaders):\n                            v = dict(v)\n                        elif isinstance(\n                            v, elastic_transport.OpenTelemetrySpan\n                        ):\n                            # the transport Span is always a dummy one\n                            v = None\n                        return (k, v)\n\n                    hook_kwargs = dict(\n                        normalize_kwargs(k, v) for k, v in kwargs.items()\n                    )\n                else:\n                    hook_kwargs = kwargs\n                request_hook(span, method, url, hook_kwargs)\n\n            if span.is_recording():\n                attributes = {\n                    SpanAttributes.DB_SYSTEM: \"elasticsearch\",\n                }\n                if url:\n                    attributes[\"elasticsearch.url\"] = url\n                if method:\n                    attributes[\"elasticsearch.method\"] = method\n                if body:\n                    # Don't set db.statement for bulk requests, as it can be very large\n                    if isinstance(body, dict):\n                        attributes[SpanAttributes.DB_STATEMENT] = (\n                            sanitize_body(body)\n                        )\n                if params:\n                    attributes[\"elasticsearch.params\"] = str(params)\n                if doc_id:\n                    attributes[\"elasticsearch.id\"] = doc_id\n                if search_target:\n                    attributes[\"elasticsearch.target\"] = search_target\n                for key, value in attributes.items():\n                    span.set_attribute(key, value)\n\n            rv = wrapped(*args, **kwargs)\n\n            body = rv.body if es_transport_split else rv\n            if isinstance(body, dict) and span.is_recording():\n                for member in _ATTRIBUTES_FROM_RESULT:\n                    if member in body:\n                        span.set_attribute(\n                            f\"elasticsearch.{member}\",\n                            str(body[member]),\n                        )\n\n            # since the transport split the raising of exceptions that set the error status\n            # are called after this code so need to set error status manually\n            if es_transport_split and span.is_recording():\n                if not (method == \"HEAD\" and rv.meta.status == 404) and (\n                    not 200 <= rv.meta.status < 299\n                ):\n                    exception = elasticsearch.exceptions.HTTP_EXCEPTIONS.get(\n                        rv.meta.status, elasticsearch.exceptions.ApiError\n                    )\n                    message = str(body)\n                    if isinstance(body, dict):\n                        error = body.get(\"error\", message)\n                        if isinstance(error, dict) and \"type\" in error:\n                            error = error[\"type\"]\n                        message = error\n\n                    span.set_status(\n                        Status(\n                            status_code=StatusCode.ERROR,\n                            description=f\"{exception.__name__}: {message}\",\n                        )\n                    )\n\n            if callable(response_hook):\n                response_hook(span, body)\n            return rv\n\n    return wrapper\n", "path": "instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/__init__.py"}]}