We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
elasticsearch span_name not correctly

**Steps to reproduce**
Describe exactly how to reproduce the error. Include a code sample if applicable.

**What is the expected behavior?**
Our index is created on a rolling basis
Therefore, we will use a specific time to query the index when querying, and when the plugin generates span_name, it will bring the unified index, so that span_name cannot be described as an operation. As follows
```
Elasticsearch/bkfta_action_20220228_read,bkfta_action_20220301_read,bkfta_action_20220302_read,bkfta_action_20220303_read,bkfta_action_20220304_read,bkfta_action_20220305_read,bkfta_action_20220306_read,bkfta_action_20220307_read/_search
```
**What is the actual behavior?**
span_name shuld be
```
Elasticsearch/{index_name}/_search
```

**Additional context**
Add any other context about the problem here.


--- 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 
46 The `instrument` method accepts the following keyword args:
47 
48 tracer_provider (TracerProvider) - an optional tracer provider
49 request_hook (Callable) - a function with extra user-defined logic to be performed before performing the request
50                           this function signature is:
51                           def request_hook(span: Span, method: str, url: str, kwargs)
52 response_hook (Callable) - a function with extra user-defined logic to be performed after performing the request
53                           this function signature is:
54                           def response_hook(span: Span, response: dict)
55 
56 for example:
57 
58 .. code: python
59 
60     from opentelemetry.instrumentation.elasticsearch import ElasticsearchInstrumentor
61     import elasticsearch
62 
63     def request_hook(span, method, url, kwargs):
64         if span and span.is_recording():
65             span.set_attribute("custom_user_attribute_from_request_hook", "some-value")
66 
67     def response_hook(span, response):
68         if span and span.is_recording():
69             span.set_attribute("custom_user_attribute_from_response_hook", "some-value")
70 
71     # instrument elasticsearch with request and response hooks
72     ElasticsearchInstrumentor().instrument(request_hook=request_hook, response_hook=response_hook)
73 
74     # Using elasticsearch as normal now will automatically generate spans,
75     # including user custom attributes added from the hooks
76     es = elasticsearch.Elasticsearch()
77     es.index(index='my-index', doc_type='my-type', id=1, body={'my': 'data', 'timestamp': datetime.now()})
78     es.get(index='my-index', doc_type='my-type', id=1)
79 
80 API
81 ---
82 """
83 
84 import re
85 from logging import getLogger
86 from os import environ
87 from typing import Collection
88 
89 import elasticsearch
90 import elasticsearch.exceptions
91 from wrapt import wrap_function_wrapper as _wrap
92 
93 from opentelemetry.instrumentation.elasticsearch.package import _instruments
94 from opentelemetry.instrumentation.elasticsearch.version import __version__
95 from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
96 from opentelemetry.instrumentation.utils import unwrap
97 from opentelemetry.semconv.trace import SpanAttributes
98 from opentelemetry.trace import SpanKind, get_tracer
99 
100 logger = getLogger(__name__)
101 
102 
103 # Values to add as tags from the actual
104 # payload returned by Elasticsearch, if any.
105 _ATTRIBUTES_FROM_RESULT = [
106     "found",
107     "timed_out",
108     "took",
109 ]
110 
111 _DEFAULT_OP_NAME = "request"
112 
113 
114 class ElasticsearchInstrumentor(BaseInstrumentor):
115     """An instrumentor for elasticsearch
116     See `BaseInstrumentor`
117     """
118 
119     def __init__(self, span_name_prefix=None):
120         if not span_name_prefix:
121             span_name_prefix = environ.get(
122                 "OTEL_PYTHON_ELASTICSEARCH_NAME_PREFIX",
123                 "Elasticsearch",
124             )
125         self._span_name_prefix = span_name_prefix.strip()
126         super().__init__()
127 
128     def instrumentation_dependencies(self) -> Collection[str]:
129         return _instruments
130 
131     def _instrument(self, **kwargs):
132         """
133         Instruments elasticsarch module
134         """
135         tracer_provider = kwargs.get("tracer_provider")
136         tracer = get_tracer(__name__, __version__, tracer_provider)
137         request_hook = kwargs.get("request_hook")
138         response_hook = kwargs.get("response_hook")
139         _wrap(
140             elasticsearch,
141             "Transport.perform_request",
142             _wrap_perform_request(
143                 tracer, self._span_name_prefix, request_hook, response_hook
144             ),
145         )
146 
147     def _uninstrument(self, **kwargs):
148         unwrap(elasticsearch.Transport, "perform_request")
149 
150 
151 _regex_doc_url = re.compile(r"/_doc/([^/]+)")
152 
153 
154 def _wrap_perform_request(
155     tracer, span_name_prefix, request_hook=None, response_hook=None
156 ):
157     # pylint: disable=R0912
158     def wrapper(wrapped, _, args, kwargs):
159         method = url = None
160         try:
161             method, url, *_ = args
162         except IndexError:
163             logger.warning(
164                 "expected perform_request to receive two positional arguments. "
165                 "Got %d",
166                 len(args),
167             )
168 
169         op_name = span_name_prefix + (url or method or _DEFAULT_OP_NAME)
170         doc_id = None
171         if url:
172             # 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()
173             # A limitation of the regex is that only the '_doc' mapping type is supported. Mapping types are deprecated since Elasticsearch 7
174             # https://github.com/open-telemetry/opentelemetry-python-contrib/issues/708
175             match = _regex_doc_url.search(url)
176             if match is not None:
177                 # Remove the full document ID from the URL
178                 doc_span = match.span()
179                 op_name = (
180                     span_name_prefix
181                     + url[: doc_span[0]]
182                     + "/_doc/:id"
183                     + url[doc_span[1] :]
184                 )
185                 # Put the document ID in attributes
186                 doc_id = match.group(1)
187         params = kwargs.get("params", {})
188         body = kwargs.get("body", None)
189 
190         with tracer.start_as_current_span(
191             op_name,
192             kind=SpanKind.CLIENT,
193         ) as span:
194 
195             if callable(request_hook):
196                 request_hook(span, method, url, kwargs)
197 
198             if span.is_recording():
199                 attributes = {
200                     SpanAttributes.DB_SYSTEM: "elasticsearch",
201                 }
202                 if url:
203                     attributes["elasticsearch.url"] = url
204                 if method:
205                     attributes["elasticsearch.method"] = method
206                 if body:
207                     attributes[SpanAttributes.DB_STATEMENT] = str(body)
208                 if params:
209                     attributes["elasticsearch.params"] = str(params)
210                 if doc_id:
211                     attributes["elasticsearch.id"] = doc_id
212                 for key, value in attributes.items():
213                     span.set_attribute(key, value)
214 
215             rv = wrapped(*args, **kwargs)
216             if isinstance(rv, dict) and span.is_recording():
217                 for member in _ATTRIBUTES_FROM_RESULT:
218                     if member in rv:
219                         span.set_attribute(
220                             f"elasticsearch.{member}",
221                             str(rv[member]),
222                         )
223 
224             if callable(response_hook):
225                 response_hook(span, rv)
226             return rv
227 
228     return wrapper
229
```
--- 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
@@ -150,11 +150,14 @@
 
 _regex_doc_url = re.compile(r"/_doc/([^/]+)")
 
+# search api https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html
+_regex_search_url = re.compile(r"/([^/]+)/_search[/]?")
+
 
 def _wrap_perform_request(
     tracer, span_name_prefix, request_hook=None, response_hook=None
 ):
-    # pylint: disable=R0912
+    # pylint: disable=R0912,R0914
     def wrapper(wrapped, _, args, kwargs):
         method = url = None
         try:
@@ -167,7 +170,10 @@
             )
 
         op_name = span_name_prefix + (url or method or _DEFAULT_OP_NAME)
+
         doc_id = None
+        search_target = None
+
         if url:
             # 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()
             # A limitation of the regex is that only the '_doc' mapping type is supported. Mapping types are deprecated since Elasticsearch 7
@@ -184,6 +190,11 @@
                 )
                 # Put the document ID in attributes
                 doc_id = match.group(1)
+            match = _regex_search_url.search(url)
+            if match is not None:
+                op_name = span_name_prefix + "/<target>/_search"
+                search_target = match.group(1)
+
         params = kwargs.get("params", {})
         body = kwargs.get("body", None)
 
@@ -209,6 +220,8 @@
                     attributes["elasticsearch.params"] = str(params)
                 if doc_id:
                     attributes["elasticsearch.id"] = doc_id
+                if search_target:
+                    attributes["elasticsearch.target"] = search_target
                 for key, value in attributes.items():
                     span.set_attribute(key, value)

{"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@@ -150,11 +150,14 @@\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 def _wrap_perform_request(\n     tracer, span_name_prefix, request_hook=None, response_hook=None\n ):\n-    # pylint: disable=R0912\n+    # pylint: disable=R0912,R0914\n     def wrapper(wrapped, _, args, kwargs):\n         method = url = None\n         try:\n@@ -167,7 +170,10 @@\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@@ -184,6 +190,11 @@\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@@ -209,6 +220,8 @@\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", "issue": "elasticsearch span_name not correctly\n\r\n**Steps to reproduce**\r\nDescribe exactly how to reproduce the error. Include a code sample if applicable.\r\n\r\n**What is the expected behavior?**\r\nOur index is created on a rolling basis\r\nTherefore, we will use a specific time to query the index when querying, and when the plugin generates span_name, it will bring the unified index, so that span_name cannot be described as an operation. As follows\r\n```\r\nElasticsearch/bkfta_action_20220228_read,bkfta_action_20220301_read,bkfta_action_20220302_read,bkfta_action_20220303_read,bkfta_action_20220304_read,bkfta_action_20220305_read,bkfta_action_20220306_read,bkfta_action_20220307_read/_search\r\n```\r\n**What is the actual behavior?**\r\nspan_name shuld be\r\n```\r\nElasticsearch/{index_name}/_search\r\n```\r\n\r\n**Additional context**\r\nAdd any other context about the problem here.\r\n\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\n\nThe `instrument` method accepts the following keyword args:\n\ntracer_provider (TracerProvider) - an optional tracer provider\nrequest_hook (Callable) - a function with extra user-defined logic to be performed before performing the request\n                          this function signature is:\n                          def request_hook(span: Span, method: str, url: str, kwargs)\nresponse_hook (Callable) - a function with extra user-defined logic to be performed after performing the request\n                          this function signature is:\n                          def 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, get_tracer\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 elasticsarch module\n        \"\"\"\n        tracer_provider = kwargs.get(\"tracer_provider\")\n        tracer = get_tracer(__name__, __version__, tracer_provider)\n        request_hook = kwargs.get(\"request_hook\")\n        response_hook = kwargs.get(\"response_hook\")\n        _wrap(\n            elasticsearch,\n            \"Transport.perform_request\",\n            _wrap_perform_request(\n                tracer, self._span_name_prefix, request_hook, response_hook\n            ),\n        )\n\n    def _uninstrument(self, **kwargs):\n        unwrap(elasticsearch.Transport, \"perform_request\")\n\n\n_regex_doc_url = re.compile(r\"/_doc/([^/]+)\")\n\n\ndef _wrap_perform_request(\n    tracer, span_name_prefix, request_hook=None, response_hook=None\n):\n    # pylint: disable=R0912\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        doc_id = None\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        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\n            if callable(request_hook):\n                request_hook(span, method, url, 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                    attributes[SpanAttributes.DB_STATEMENT] = str(body)\n                if params:\n                    attributes[\"elasticsearch.params\"] = str(params)\n                if doc_id:\n                    attributes[\"elasticsearch.id\"] = doc_id\n                for key, value in attributes.items():\n                    span.set_attribute(key, value)\n\n            rv = wrapped(*args, **kwargs)\n            if isinstance(rv, dict) and span.is_recording():\n                for member in _ATTRIBUTES_FROM_RESULT:\n                    if member in rv:\n                        span.set_attribute(\n                            f\"elasticsearch.{member}\",\n                            str(rv[member]),\n                        )\n\n            if callable(response_hook):\n                response_hook(span, rv)\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\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\n\nThe `instrument` method accepts the following keyword args:\n\ntracer_provider (TracerProvider) - an optional tracer provider\nrequest_hook (Callable) - a function with extra user-defined logic to be performed before performing the request\n                          this function signature is:\n                          def request_hook(span: Span, method: str, url: str, kwargs)\nresponse_hook (Callable) - a function with extra user-defined logic to be performed after performing the request\n                          this function signature is:\n                          def 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, get_tracer\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 elasticsarch module\n        \"\"\"\n        tracer_provider = kwargs.get(\"tracer_provider\")\n        tracer = get_tracer(__name__, __version__, tracer_provider)\n        request_hook = kwargs.get(\"request_hook\")\n        response_hook = kwargs.get(\"response_hook\")\n        _wrap(\n            elasticsearch,\n            \"Transport.perform_request\",\n            _wrap_perform_request(\n                tracer, self._span_name_prefix, request_hook, response_hook\n            ),\n        )\n\n    def _uninstrument(self, **kwargs):\n        unwrap(elasticsearch.Transport, \"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\ndef _wrap_perform_request(\n    tracer, span_name_prefix, request_hook=None, 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\n            if callable(request_hook):\n                request_hook(span, method, url, 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                    attributes[SpanAttributes.DB_STATEMENT] = str(body)\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            if isinstance(rv, dict) and span.is_recording():\n                for member in _ATTRIBUTES_FROM_RESULT:\n                    if member in rv:\n                        span.set_attribute(\n                            f\"elasticsearch.{member}\",\n                            str(rv[member]),\n                        )\n\n            if callable(response_hook):\n                response_hook(span, rv)\n            return rv\n\n    return wrapper\n", "path": "instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/__init__.py"}]}