We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Add readthedocs documentation for elasticsearch instrumentation
Part of [1491](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/1491)

--- 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 Elasticsearch 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 # search api https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html
154 _regex_search_url = re.compile(r"/([^/]+)/_search[/]?")
155 
156 
157 def _wrap_perform_request(
158     tracer, span_name_prefix, request_hook=None, response_hook=None
159 ):
160     # pylint: disable=R0912,R0914
161     def wrapper(wrapped, _, args, kwargs):
162         method = url = None
163         try:
164             method, url, *_ = args
165         except IndexError:
166             logger.warning(
167                 "expected perform_request to receive two positional arguments. "
168                 "Got %d",
169                 len(args),
170             )
171 
172         op_name = span_name_prefix + (url or method or _DEFAULT_OP_NAME)
173 
174         doc_id = None
175         search_target = None
176 
177         if url:
178             # 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()
179             # A limitation of the regex is that only the '_doc' mapping type is supported. Mapping types are deprecated since Elasticsearch 7
180             # https://github.com/open-telemetry/opentelemetry-python-contrib/issues/708
181             match = _regex_doc_url.search(url)
182             if match is not None:
183                 # Remove the full document ID from the URL
184                 doc_span = match.span()
185                 op_name = (
186                     span_name_prefix
187                     + url[: doc_span[0]]
188                     + "/_doc/:id"
189                     + url[doc_span[1] :]
190                 )
191                 # Put the document ID in attributes
192                 doc_id = match.group(1)
193             match = _regex_search_url.search(url)
194             if match is not None:
195                 op_name = span_name_prefix + "/<target>/_search"
196                 search_target = match.group(1)
197 
198         params = kwargs.get("params", {})
199         body = kwargs.get("body", None)
200 
201         with tracer.start_as_current_span(
202             op_name,
203             kind=SpanKind.CLIENT,
204         ) as span:
205 
206             if callable(request_hook):
207                 request_hook(span, method, url, kwargs)
208 
209             if span.is_recording():
210                 attributes = {
211                     SpanAttributes.DB_SYSTEM: "elasticsearch",
212                 }
213                 if url:
214                     attributes["elasticsearch.url"] = url
215                 if method:
216                     attributes["elasticsearch.method"] = method
217                 if body:
218                     attributes[SpanAttributes.DB_STATEMENT] = str(body)
219                 if params:
220                     attributes["elasticsearch.params"] = str(params)
221                 if doc_id:
222                     attributes["elasticsearch.id"] = doc_id
223                 if search_target:
224                     attributes["elasticsearch.target"] = search_target
225                 for key, value in attributes.items():
226                     span.set_attribute(key, value)
227 
228             rv = wrapped(*args, **kwargs)
229             if isinstance(rv, dict) and span.is_recording():
230                 for member in _ATTRIBUTES_FROM_RESULT:
231                     if member in rv:
232                         span.set_attribute(
233                             f"elasticsearch.{member}",
234                             str(rv[member]),
235                         )
236 
237             if callable(response_hook):
238                 response_hook(span, rv)
239             return rv
240 
241     return wrapper
242
```
--- 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
@@ -34,7 +34,7 @@
     es.get(index='my-index', doc_type='my-type', id=1)
 
 Elasticsearch instrumentation prefixes operation names with the string "Elasticsearch". This
-can be changed to a different string by either setting the `OTEL_PYTHON_ELASTICSEARCH_NAME_PREFIX`
+can be changed to a different string by either setting the OTEL_PYTHON_ELASTICSEARCH_NAME_PREFIX
 environment variable or by passing the prefix as an argument to the instrumentor. For example,
 
 
@@ -42,16 +42,15 @@
 
     ElasticsearchInstrumentor("my-custom-prefix").instrument()
 
-
-The `instrument` method accepts the following keyword args:
-
+The instrument() method accepts the following keyword args:
 tracer_provider (TracerProvider) - an optional tracer provider
 request_hook (Callable) - a function with extra user-defined logic to be performed before performing the request
-                          this function signature is:
-                          def request_hook(span: Span, method: str, url: str, kwargs)
+this function signature is:
+def request_hook(span: Span, method: str, url: str, kwargs)
+
 response_hook (Callable) - a function with extra user-defined logic to be performed after performing the request
-                          this function signature is:
-                          def response_hook(span: Span, response: dict)
+this function signature is:
+def response_hook(span: Span, response: dict)
 
 for example:

{"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@@ -34,7 +34,7 @@\n     es.get(index='my-index', doc_type='my-type', id=1)\n \n Elasticsearch instrumentation prefixes operation names with the string \"Elasticsearch\". This\n-can be changed to a different string by either setting the `OTEL_PYTHON_ELASTICSEARCH_NAME_PREFIX`\n+can be changed to a different string by either setting the OTEL_PYTHON_ELASTICSEARCH_NAME_PREFIX\n environment variable or by passing the prefix as an argument to the instrumentor. For example,\n \n \n@@ -42,16 +42,15 @@\n \n     ElasticsearchInstrumentor(\"my-custom-prefix\").instrument()\n \n-\n-The `instrument` method accepts the following keyword args:\n-\n+The instrument() method accepts the following keyword args:\n tracer_provider (TracerProvider) - an optional tracer provider\n request_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)\n+this function signature is:\n+def request_hook(span: Span, method: str, url: str, kwargs)\n+\n response_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+this function signature is:\n+def response_hook(span: Span, response: dict)\n \n for example:\n", "issue": "Add readthedocs documentation for elasticsearch instrumentation\nPart of [1491](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/1491)\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 Elasticsearch 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"}], "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\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, 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 Elasticsearch 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"}]}