We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
new vertion 2.26 No module named 'elasticsearch' 
after updating to scout-apm to 2.26

we got ModuleNotFoundError: No module named 'elasticsearch'

should be easy to fix ,, good luck ^_^

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/scout_apm/instruments/elasticsearch.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import logging
5 from collections import namedtuple
6 
7 import elasticsearch
8 import wrapt
9 
10 from scout_apm.compat import get_pos_args, unwrap_decorators
11 from scout_apm.core.tracked_request import TrackedRequest
12 
13 try:
14     from elasticsearch import Elasticsearch
15 except ImportError:  # pragma: no cover
16     Elasticsearch = None
17 
18 try:
19     # Transport was moved to elastic_transport as of v8.0.0
20     from elastic_transport import Transport
21 except ImportError:  # pragma: no cover
22     try:
23         from elasticsearch import Transport
24     except ImportError:  # pragma: no cover
25         Transport = None
26 
27 logger = logging.getLogger(__name__)
28 
29 
30 def ensure_installed():
31     logger.debug("Instrumenting elasticsearch.")
32 
33     if Elasticsearch is None or Transport is None:
34         if Elasticsearch is None:
35             logger.debug(
36                 "Couldn't import elasticsearch.Elasticsearch "
37                 "- probably not installed."
38             )
39         if Transport is None:
40             logger.debug(
41                 "Couldn't import elasticsearch.Transport or "
42                 "elastic_transport.Transport - probably not installed."
43             )
44     else:
45         ensure_client_instrumented()
46         ensure_transport_instrumented()
47 
48 
49 ClientMethod = namedtuple("ClientMethod", ["name", "takes_index_argument"])
50 
51 VERSIONED_CLIENT_METHODS = {
52     "v7": [
53         ClientMethod("bulk", True),
54         ClientMethod("clear_scroll", False),
55         ClientMethod("close", False),
56         ClientMethod("close_point_in_time", False),
57         ClientMethod("count", True),
58         ClientMethod("create", True),
59         ClientMethod("delete", True),
60         ClientMethod("delete_by_query", True),
61         ClientMethod("delete_by_query_rethrottle", False),
62         ClientMethod("delete_script", False),
63         ClientMethod("exists", True),
64         ClientMethod("exists_source", True),
65         ClientMethod("explain", True),
66         ClientMethod("field_caps", True),
67         ClientMethod("get", True),
68         ClientMethod("get_script", False),
69         ClientMethod("get_script_context", False),
70         ClientMethod("get_script_languages", False),
71         ClientMethod("get_source", True),
72         ClientMethod("index", True),
73         ClientMethod("info", False),
74         ClientMethod("mget", True),
75         ClientMethod("msearch", True),
76         ClientMethod("msearch_template", True),
77         ClientMethod("mtermvectors", True),
78         ClientMethod("open_point_in_time", True),
79         ClientMethod("ping", False),
80         ClientMethod("put_script", False),
81         ClientMethod("rank_eval", True),
82         ClientMethod("reindex", False),
83         ClientMethod("reindex_rethrottle", False),
84         ClientMethod("render_search_template", False),
85         ClientMethod("scripts_painless_execute", False),
86         ClientMethod("scroll", False),
87         ClientMethod("search", True),
88         ClientMethod("search_mvt", True),
89         ClientMethod("search_shards", True),
90         ClientMethod("search_template", True),
91         ClientMethod("termvectors", True),
92         ClientMethod("terms_enum", True),
93         ClientMethod("update", True),
94         ClientMethod("update_by_query", True),
95         ClientMethod("update_by_query_rethrottle", False),
96     ],
97     "v8": [
98         ClientMethod("knn_search", True),
99     ],
100 }
101 
102 CLIENT_METHODS = VERSIONED_CLIENT_METHODS["v7"][:]
103 if elasticsearch.VERSION > (8, 0, 0):
104     CLIENT_METHODS += VERSIONED_CLIENT_METHODS["v8"]
105 
106 
107 have_patched_client = False
108 
109 
110 def ensure_client_instrumented():
111     global have_patched_client
112 
113     if not have_patched_client:
114         instrumented_count = 0
115         for name, takes_index_argument in CLIENT_METHODS:
116             try:
117                 method = getattr(Elasticsearch, name)
118                 if takes_index_argument:
119                     wrapped = wrap_client_index_method(method)
120                 else:
121                     wrapped = wrap_client_method(method)
122                 setattr(Elasticsearch, name, wrapped)
123                 instrumented_count += 1
124             except Exception as exc:
125                 logger.debug(
126                     "Failed to instrument elasticsearch.Elasticsearch.%s: %r",
127                     name,
128                     exc,
129                     exc_info=exc,
130                 )
131         if instrumented_count == 0:
132             logger.warning(
133                 "Failed to instrument any elasticsearch.Elasticsearch methods."
134                 " Enable debug logs to view root causes."
135             )
136 
137         have_patched_client = True
138 
139 
140 @wrapt.decorator
141 def wrap_client_index_method(wrapped, instance, args, kwargs):
142     # elasticsearch-py 7.5.1 changed the order of arguments for client methods,
143     # so to be safe we need to inspect the wrapped method's positional
144     # arguments to see if we should pull it from there
145     if "index" in kwargs:
146         index = kwargs["index"]
147     else:
148         unwrapped = unwrap_decorators(wrapped)
149         pos_args = get_pos_args(unwrapped)
150         try:
151             index_index = pos_args.index("index")
152         except ValueError:  # pragma: no cover
153             # This guards against the method not accepting an 'index' argument
154             # but they all do - for now
155             index = ""
156         else:
157             try:
158                 index = args[index_index - 1]  # subtract 'self'
159             except IndexError:
160                 index = ""
161 
162     if isinstance(index, (list, tuple)):
163         index = ",".join(index)
164     if index == "":
165         index = "Unknown"
166     index = index.title()
167 
168     camel_name = "".join(c.title() for c in wrapped.__name__.split("_"))
169     operation = "Elasticsearch/{}/{}".format(index, camel_name)
170     tracked_request = TrackedRequest.instance()
171     with tracked_request.span(operation=operation, ignore_children=True):
172         return wrapped(*args, **kwargs)
173 
174 
175 @wrapt.decorator
176 def wrap_client_method(wrapped, instance, args, kwargs):
177     camel_name = "".join(c.title() for c in wrapped.__name__.split("_"))
178     operation = "Elasticsearch/{}".format(camel_name)
179     tracked_request = TrackedRequest.instance()
180     with tracked_request.span(operation=operation, ignore_children=True):
181         return wrapped(*args, **kwargs)
182 
183 
184 have_patched_transport = False
185 
186 
187 def ensure_transport_instrumented():
188     global have_patched_transport
189 
190     if not have_patched_transport:
191         try:
192             Transport.perform_request = wrapped_perform_request(
193                 Transport.perform_request
194             )
195         except Exception as exc:
196             logger.warning(
197                 "Failed to instrument elasticsearch.Transport.perform_request: %r",
198                 exc,
199                 exc_info=exc,
200             )
201 
202     have_patched_transport = True
203 
204 
205 def _sanitize_name(name):
206     try:
207         op = name.split("/")[-1]
208         op = op[1:]  # chop leading '_' from op
209         known_names = (
210             "bench",
211             "bulk",
212             "count",
213             "delete_by_query",
214             "execute",
215             "exists",
216             "explain",
217             "field_caps",
218             "field_stats",
219             "health",
220             "mget",
221             "mlt",
222             "mpercolate",
223             "msearch",
224             "mtermvectors",
225             "percolate",
226             "pit",
227             "query",
228             "rank_eval",
229             "reindex",
230             "script_context",
231             "script_language",
232             "scroll",
233             "search_shards",
234             "source",
235             "suggest",
236             "template",
237             "termvectors",
238             "terms_enum",
239             "update",
240             "update_by_query",
241             "search",
242         )
243         if op in known_names:
244             return op.title()
245         return "Unknown"
246     except Exception:
247         return "Unknown"
248 
249 
250 @wrapt.decorator
251 def wrapped_perform_request(wrapped, instance, args, kwargs):
252     try:
253         op = _sanitize_name(args[1])
254     except IndexError:
255         op = "Unknown"
256 
257     tracked_request = TrackedRequest.instance()
258     with tracked_request.span(
259         operation="Elasticsearch/{}".format(op),
260         ignore_children=True,
261     ):
262         return wrapped(*args, **kwargs)
263
```
--- 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/src/scout_apm/instruments/elasticsearch.py b/src/scout_apm/instruments/elasticsearch.py
--- a/src/scout_apm/instruments/elasticsearch.py
+++ b/src/scout_apm/instruments/elasticsearch.py
@@ -4,16 +4,17 @@
 import logging
 from collections import namedtuple
 
-import elasticsearch
 import wrapt
 
 from scout_apm.compat import get_pos_args, unwrap_decorators
 from scout_apm.core.tracked_request import TrackedRequest
 
 try:
+    from elasticsearch import VERSION as ELASTICSEARCH_VERSION
     from elasticsearch import Elasticsearch
 except ImportError:  # pragma: no cover
     Elasticsearch = None
+    ELASTICSEARCH_VERSION = (0, 0, 0)
 
 try:
     # Transport was moved to elastic_transport as of v8.0.0
@@ -100,7 +101,7 @@
 }
 
 CLIENT_METHODS = VERSIONED_CLIENT_METHODS["v7"][:]
-if elasticsearch.VERSION > (8, 0, 0):
+if ELASTICSEARCH_VERSION > (8, 0, 0):
     CLIENT_METHODS += VERSIONED_CLIENT_METHODS["v8"]

{"golden_diff": "diff --git a/src/scout_apm/instruments/elasticsearch.py b/src/scout_apm/instruments/elasticsearch.py\n--- a/src/scout_apm/instruments/elasticsearch.py\n+++ b/src/scout_apm/instruments/elasticsearch.py\n@@ -4,16 +4,17 @@\n import logging\n from collections import namedtuple\n \n-import elasticsearch\n import wrapt\n \n from scout_apm.compat import get_pos_args, unwrap_decorators\n from scout_apm.core.tracked_request import TrackedRequest\n \n try:\n+    from elasticsearch import VERSION as ELASTICSEARCH_VERSION\n     from elasticsearch import Elasticsearch\n except ImportError:  # pragma: no cover\n     Elasticsearch = None\n+    ELASTICSEARCH_VERSION = (0, 0, 0)\n \n try:\n     # Transport was moved to elastic_transport as of v8.0.0\n@@ -100,7 +101,7 @@\n }\n \n CLIENT_METHODS = VERSIONED_CLIENT_METHODS[\"v7\"][:]\n-if elasticsearch.VERSION > (8, 0, 0):\n+if ELASTICSEARCH_VERSION > (8, 0, 0):\n     CLIENT_METHODS += VERSIONED_CLIENT_METHODS[\"v8\"]\n", "issue": "new vertion 2.26 No module named 'elasticsearch' \nafter updating to scout-apm to 2.26\r\n\r\nwe got ModuleNotFoundError: No module named 'elasticsearch'\r\n\r\nshould be easy to fix ,, good luck ^_^\n", "before_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport logging\nfrom collections import namedtuple\n\nimport elasticsearch\nimport wrapt\n\nfrom scout_apm.compat import get_pos_args, unwrap_decorators\nfrom scout_apm.core.tracked_request import TrackedRequest\n\ntry:\n    from elasticsearch import Elasticsearch\nexcept ImportError:  # pragma: no cover\n    Elasticsearch = None\n\ntry:\n    # Transport was moved to elastic_transport as of v8.0.0\n    from elastic_transport import Transport\nexcept ImportError:  # pragma: no cover\n    try:\n        from elasticsearch import Transport\n    except ImportError:  # pragma: no cover\n        Transport = None\n\nlogger = logging.getLogger(__name__)\n\n\ndef ensure_installed():\n    logger.debug(\"Instrumenting elasticsearch.\")\n\n    if Elasticsearch is None or Transport is None:\n        if Elasticsearch is None:\n            logger.debug(\n                \"Couldn't import elasticsearch.Elasticsearch \"\n                \"- probably not installed.\"\n            )\n        if Transport is None:\n            logger.debug(\n                \"Couldn't import elasticsearch.Transport or \"\n                \"elastic_transport.Transport - probably not installed.\"\n            )\n    else:\n        ensure_client_instrumented()\n        ensure_transport_instrumented()\n\n\nClientMethod = namedtuple(\"ClientMethod\", [\"name\", \"takes_index_argument\"])\n\nVERSIONED_CLIENT_METHODS = {\n    \"v7\": [\n        ClientMethod(\"bulk\", True),\n        ClientMethod(\"clear_scroll\", False),\n        ClientMethod(\"close\", False),\n        ClientMethod(\"close_point_in_time\", False),\n        ClientMethod(\"count\", True),\n        ClientMethod(\"create\", True),\n        ClientMethod(\"delete\", True),\n        ClientMethod(\"delete_by_query\", True),\n        ClientMethod(\"delete_by_query_rethrottle\", False),\n        ClientMethod(\"delete_script\", False),\n        ClientMethod(\"exists\", True),\n        ClientMethod(\"exists_source\", True),\n        ClientMethod(\"explain\", True),\n        ClientMethod(\"field_caps\", True),\n        ClientMethod(\"get\", True),\n        ClientMethod(\"get_script\", False),\n        ClientMethod(\"get_script_context\", False),\n        ClientMethod(\"get_script_languages\", False),\n        ClientMethod(\"get_source\", True),\n        ClientMethod(\"index\", True),\n        ClientMethod(\"info\", False),\n        ClientMethod(\"mget\", True),\n        ClientMethod(\"msearch\", True),\n        ClientMethod(\"msearch_template\", True),\n        ClientMethod(\"mtermvectors\", True),\n        ClientMethod(\"open_point_in_time\", True),\n        ClientMethod(\"ping\", False),\n        ClientMethod(\"put_script\", False),\n        ClientMethod(\"rank_eval\", True),\n        ClientMethod(\"reindex\", False),\n        ClientMethod(\"reindex_rethrottle\", False),\n        ClientMethod(\"render_search_template\", False),\n        ClientMethod(\"scripts_painless_execute\", False),\n        ClientMethod(\"scroll\", False),\n        ClientMethod(\"search\", True),\n        ClientMethod(\"search_mvt\", True),\n        ClientMethod(\"search_shards\", True),\n        ClientMethod(\"search_template\", True),\n        ClientMethod(\"termvectors\", True),\n        ClientMethod(\"terms_enum\", True),\n        ClientMethod(\"update\", True),\n        ClientMethod(\"update_by_query\", True),\n        ClientMethod(\"update_by_query_rethrottle\", False),\n    ],\n    \"v8\": [\n        ClientMethod(\"knn_search\", True),\n    ],\n}\n\nCLIENT_METHODS = VERSIONED_CLIENT_METHODS[\"v7\"][:]\nif elasticsearch.VERSION > (8, 0, 0):\n    CLIENT_METHODS += VERSIONED_CLIENT_METHODS[\"v8\"]\n\n\nhave_patched_client = False\n\n\ndef ensure_client_instrumented():\n    global have_patched_client\n\n    if not have_patched_client:\n        instrumented_count = 0\n        for name, takes_index_argument in CLIENT_METHODS:\n            try:\n                method = getattr(Elasticsearch, name)\n                if takes_index_argument:\n                    wrapped = wrap_client_index_method(method)\n                else:\n                    wrapped = wrap_client_method(method)\n                setattr(Elasticsearch, name, wrapped)\n                instrumented_count += 1\n            except Exception as exc:\n                logger.debug(\n                    \"Failed to instrument elasticsearch.Elasticsearch.%s: %r\",\n                    name,\n                    exc,\n                    exc_info=exc,\n                )\n        if instrumented_count == 0:\n            logger.warning(\n                \"Failed to instrument any elasticsearch.Elasticsearch methods.\"\n                \" Enable debug logs to view root causes.\"\n            )\n\n        have_patched_client = True\n\n\n@wrapt.decorator\ndef wrap_client_index_method(wrapped, instance, args, kwargs):\n    # elasticsearch-py 7.5.1 changed the order of arguments for client methods,\n    # so to be safe we need to inspect the wrapped method's positional\n    # arguments to see if we should pull it from there\n    if \"index\" in kwargs:\n        index = kwargs[\"index\"]\n    else:\n        unwrapped = unwrap_decorators(wrapped)\n        pos_args = get_pos_args(unwrapped)\n        try:\n            index_index = pos_args.index(\"index\")\n        except ValueError:  # pragma: no cover\n            # This guards against the method not accepting an 'index' argument\n            # but they all do - for now\n            index = \"\"\n        else:\n            try:\n                index = args[index_index - 1]  # subtract 'self'\n            except IndexError:\n                index = \"\"\n\n    if isinstance(index, (list, tuple)):\n        index = \",\".join(index)\n    if index == \"\":\n        index = \"Unknown\"\n    index = index.title()\n\n    camel_name = \"\".join(c.title() for c in wrapped.__name__.split(\"_\"))\n    operation = \"Elasticsearch/{}/{}\".format(index, camel_name)\n    tracked_request = TrackedRequest.instance()\n    with tracked_request.span(operation=operation, ignore_children=True):\n        return wrapped(*args, **kwargs)\n\n\n@wrapt.decorator\ndef wrap_client_method(wrapped, instance, args, kwargs):\n    camel_name = \"\".join(c.title() for c in wrapped.__name__.split(\"_\"))\n    operation = \"Elasticsearch/{}\".format(camel_name)\n    tracked_request = TrackedRequest.instance()\n    with tracked_request.span(operation=operation, ignore_children=True):\n        return wrapped(*args, **kwargs)\n\n\nhave_patched_transport = False\n\n\ndef ensure_transport_instrumented():\n    global have_patched_transport\n\n    if not have_patched_transport:\n        try:\n            Transport.perform_request = wrapped_perform_request(\n                Transport.perform_request\n            )\n        except Exception as exc:\n            logger.warning(\n                \"Failed to instrument elasticsearch.Transport.perform_request: %r\",\n                exc,\n                exc_info=exc,\n            )\n\n    have_patched_transport = True\n\n\ndef _sanitize_name(name):\n    try:\n        op = name.split(\"/\")[-1]\n        op = op[1:]  # chop leading '_' from op\n        known_names = (\n            \"bench\",\n            \"bulk\",\n            \"count\",\n            \"delete_by_query\",\n            \"execute\",\n            \"exists\",\n            \"explain\",\n            \"field_caps\",\n            \"field_stats\",\n            \"health\",\n            \"mget\",\n            \"mlt\",\n            \"mpercolate\",\n            \"msearch\",\n            \"mtermvectors\",\n            \"percolate\",\n            \"pit\",\n            \"query\",\n            \"rank_eval\",\n            \"reindex\",\n            \"script_context\",\n            \"script_language\",\n            \"scroll\",\n            \"search_shards\",\n            \"source\",\n            \"suggest\",\n            \"template\",\n            \"termvectors\",\n            \"terms_enum\",\n            \"update\",\n            \"update_by_query\",\n            \"search\",\n        )\n        if op in known_names:\n            return op.title()\n        return \"Unknown\"\n    except Exception:\n        return \"Unknown\"\n\n\n@wrapt.decorator\ndef wrapped_perform_request(wrapped, instance, args, kwargs):\n    try:\n        op = _sanitize_name(args[1])\n    except IndexError:\n        op = \"Unknown\"\n\n    tracked_request = TrackedRequest.instance()\n    with tracked_request.span(\n        operation=\"Elasticsearch/{}\".format(op),\n        ignore_children=True,\n    ):\n        return wrapped(*args, **kwargs)\n", "path": "src/scout_apm/instruments/elasticsearch.py"}], "after_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport logging\nfrom collections import namedtuple\n\nimport wrapt\n\nfrom scout_apm.compat import get_pos_args, unwrap_decorators\nfrom scout_apm.core.tracked_request import TrackedRequest\n\ntry:\n    from elasticsearch import VERSION as ELASTICSEARCH_VERSION\n    from elasticsearch import Elasticsearch\nexcept ImportError:  # pragma: no cover\n    Elasticsearch = None\n    ELASTICSEARCH_VERSION = (0, 0, 0)\n\ntry:\n    # Transport was moved to elastic_transport as of v8.0.0\n    from elastic_transport import Transport\nexcept ImportError:  # pragma: no cover\n    try:\n        from elasticsearch import Transport\n    except ImportError:  # pragma: no cover\n        Transport = None\n\nlogger = logging.getLogger(__name__)\n\n\ndef ensure_installed():\n    logger.debug(\"Instrumenting elasticsearch.\")\n\n    if Elasticsearch is None or Transport is None:\n        if Elasticsearch is None:\n            logger.debug(\n                \"Couldn't import elasticsearch.Elasticsearch \"\n                \"- probably not installed.\"\n            )\n        if Transport is None:\n            logger.debug(\n                \"Couldn't import elasticsearch.Transport or \"\n                \"elastic_transport.Transport - probably not installed.\"\n            )\n    else:\n        ensure_client_instrumented()\n        ensure_transport_instrumented()\n\n\nClientMethod = namedtuple(\"ClientMethod\", [\"name\", \"takes_index_argument\"])\n\nVERSIONED_CLIENT_METHODS = {\n    \"v7\": [\n        ClientMethod(\"bulk\", True),\n        ClientMethod(\"clear_scroll\", False),\n        ClientMethod(\"close\", False),\n        ClientMethod(\"close_point_in_time\", False),\n        ClientMethod(\"count\", True),\n        ClientMethod(\"create\", True),\n        ClientMethod(\"delete\", True),\n        ClientMethod(\"delete_by_query\", True),\n        ClientMethod(\"delete_by_query_rethrottle\", False),\n        ClientMethod(\"delete_script\", False),\n        ClientMethod(\"exists\", True),\n        ClientMethod(\"exists_source\", True),\n        ClientMethod(\"explain\", True),\n        ClientMethod(\"field_caps\", True),\n        ClientMethod(\"get\", True),\n        ClientMethod(\"get_script\", False),\n        ClientMethod(\"get_script_context\", False),\n        ClientMethod(\"get_script_languages\", False),\n        ClientMethod(\"get_source\", True),\n        ClientMethod(\"index\", True),\n        ClientMethod(\"info\", False),\n        ClientMethod(\"mget\", True),\n        ClientMethod(\"msearch\", True),\n        ClientMethod(\"msearch_template\", True),\n        ClientMethod(\"mtermvectors\", True),\n        ClientMethod(\"open_point_in_time\", True),\n        ClientMethod(\"ping\", False),\n        ClientMethod(\"put_script\", False),\n        ClientMethod(\"rank_eval\", True),\n        ClientMethod(\"reindex\", False),\n        ClientMethod(\"reindex_rethrottle\", False),\n        ClientMethod(\"render_search_template\", False),\n        ClientMethod(\"scripts_painless_execute\", False),\n        ClientMethod(\"scroll\", False),\n        ClientMethod(\"search\", True),\n        ClientMethod(\"search_mvt\", True),\n        ClientMethod(\"search_shards\", True),\n        ClientMethod(\"search_template\", True),\n        ClientMethod(\"termvectors\", True),\n        ClientMethod(\"terms_enum\", True),\n        ClientMethod(\"update\", True),\n        ClientMethod(\"update_by_query\", True),\n        ClientMethod(\"update_by_query_rethrottle\", False),\n    ],\n    \"v8\": [\n        ClientMethod(\"knn_search\", True),\n    ],\n}\n\nCLIENT_METHODS = VERSIONED_CLIENT_METHODS[\"v7\"][:]\nif ELASTICSEARCH_VERSION > (8, 0, 0):\n    CLIENT_METHODS += VERSIONED_CLIENT_METHODS[\"v8\"]\n\n\nhave_patched_client = False\n\n\ndef ensure_client_instrumented():\n    global have_patched_client\n\n    if not have_patched_client:\n        instrumented_count = 0\n        for name, takes_index_argument in CLIENT_METHODS:\n            try:\n                method = getattr(Elasticsearch, name)\n                if takes_index_argument:\n                    wrapped = wrap_client_index_method(method)\n                else:\n                    wrapped = wrap_client_method(method)\n                setattr(Elasticsearch, name, wrapped)\n                instrumented_count += 1\n            except Exception as exc:\n                logger.debug(\n                    \"Failed to instrument elasticsearch.Elasticsearch.%s: %r\",\n                    name,\n                    exc,\n                    exc_info=exc,\n                )\n        if instrumented_count == 0:\n            logger.warning(\n                \"Failed to instrument any elasticsearch.Elasticsearch methods.\"\n                \" Enable debug logs to view root causes.\"\n            )\n\n        have_patched_client = True\n\n\n@wrapt.decorator\ndef wrap_client_index_method(wrapped, instance, args, kwargs):\n    # elasticsearch-py 7.5.1 changed the order of arguments for client methods,\n    # so to be safe we need to inspect the wrapped method's positional\n    # arguments to see if we should pull it from there\n    if \"index\" in kwargs:\n        index = kwargs[\"index\"]\n    else:\n        unwrapped = unwrap_decorators(wrapped)\n        pos_args = get_pos_args(unwrapped)\n        try:\n            index_index = pos_args.index(\"index\")\n        except ValueError:  # pragma: no cover\n            # This guards against the method not accepting an 'index' argument\n            # but they all do - for now\n            index = \"\"\n        else:\n            try:\n                index = args[index_index - 1]  # subtract 'self'\n            except IndexError:\n                index = \"\"\n\n    if isinstance(index, (list, tuple)):\n        index = \",\".join(index)\n    if index == \"\":\n        index = \"Unknown\"\n    index = index.title()\n\n    camel_name = \"\".join(c.title() for c in wrapped.__name__.split(\"_\"))\n    operation = \"Elasticsearch/{}/{}\".format(index, camel_name)\n    tracked_request = TrackedRequest.instance()\n    with tracked_request.span(operation=operation, ignore_children=True):\n        return wrapped(*args, **kwargs)\n\n\n@wrapt.decorator\ndef wrap_client_method(wrapped, instance, args, kwargs):\n    camel_name = \"\".join(c.title() for c in wrapped.__name__.split(\"_\"))\n    operation = \"Elasticsearch/{}\".format(camel_name)\n    tracked_request = TrackedRequest.instance()\n    with tracked_request.span(operation=operation, ignore_children=True):\n        return wrapped(*args, **kwargs)\n\n\nhave_patched_transport = False\n\n\ndef ensure_transport_instrumented():\n    global have_patched_transport\n\n    if not have_patched_transport:\n        try:\n            Transport.perform_request = wrapped_perform_request(\n                Transport.perform_request\n            )\n        except Exception as exc:\n            logger.warning(\n                \"Failed to instrument elasticsearch.Transport.perform_request: %r\",\n                exc,\n                exc_info=exc,\n            )\n\n    have_patched_transport = True\n\n\ndef _sanitize_name(name):\n    try:\n        op = name.split(\"/\")[-1]\n        op = op[1:]  # chop leading '_' from op\n        known_names = (\n            \"bench\",\n            \"bulk\",\n            \"count\",\n            \"delete_by_query\",\n            \"execute\",\n            \"exists\",\n            \"explain\",\n            \"field_caps\",\n            \"field_stats\",\n            \"health\",\n            \"mget\",\n            \"mlt\",\n            \"mpercolate\",\n            \"msearch\",\n            \"mtermvectors\",\n            \"percolate\",\n            \"pit\",\n            \"query\",\n            \"rank_eval\",\n            \"reindex\",\n            \"script_context\",\n            \"script_language\",\n            \"scroll\",\n            \"search_shards\",\n            \"source\",\n            \"suggest\",\n            \"template\",\n            \"termvectors\",\n            \"terms_enum\",\n            \"update\",\n            \"update_by_query\",\n            \"search\",\n        )\n        if op in known_names:\n            return op.title()\n        return \"Unknown\"\n    except Exception:\n        return \"Unknown\"\n\n\n@wrapt.decorator\ndef wrapped_perform_request(wrapped, instance, args, kwargs):\n    try:\n        op = _sanitize_name(args[1])\n    except IndexError:\n        op = \"Unknown\"\n\n    tracked_request = TrackedRequest.instance()\n    with tracked_request.span(\n        operation=\"Elasticsearch/{}\".format(op),\n        ignore_children=True,\n    ):\n        return wrapped(*args, **kwargs)\n", "path": "src/scout_apm/instruments/elasticsearch.py"}]}