We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
enabling sniff_on_start in elasticsearch() breaks elasticserach instrumentation
**Describe the bug**:
sniff_on_start in elasticsearch with apm enabled fails the request due to a null span object
```python
ERROR File /opt/venv/lib/python3.9/site-packages/django/utils/log.py, line 224, Internal Server Error: /api/test_view
Traceback (most recent call last):
  File "/opt/venv/lib/python3.9/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/opt/venv/lib/python3.9/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.9/contextlib.py", line 79, in inner
    return func(*args, **kwds)
  File "/opt/venv/lib/python3.9/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/opt/venv/lib/python3.9/site-packages/django/views/generic/base.py", line 98, in dispatch
    return handler(request, *args, **kwargs)
  File "/app/api/views.py", line 592, in get
    es = Elasticsearch(hosts=["elasticsearch"],sniff_on_start=True)
  File "/opt/venv/lib/python3.9/site-packages/elasticsearch/client/__init__.py", line 205, in __init__
    self.transport = transport_class(_normalize_hosts(hosts), **kwargs)
  File "/opt/venv/lib/python3.9/site-packages/elasticsearch/transport.py", line 200, in __init__
    self.sniff_hosts(True)
  File "/opt/venv/lib/python3.9/site-packages/elasticsearch/transport.py", line 365, in sniff_hosts
    node_info = self._get_sniff_data(initial)
  File "/opt/venv/lib/python3.9/site-packages/elasticsearch/transport.py", line 308, in _get_sniff_data
    _, headers, node_info = c.perform_request(
  File "/opt/venv/lib/python3.9/site-packages/elasticapm/instrumentation/packages/base.py", line 210, in call_if_sampling
    return self.call(module, method, wrapped, instance, args, kwargs)
  File "/opt/venv/lib/python3.9/site-packages/elasticapm/instrumentation/packages/elasticsearch.py", line 58, in call
    self._update_context_by_request_data(span.context, instance, args, kwargs)
AttributeError: 'NoneType' object has no attribute 'context'

```


**To Reproduce**

1. setup an elasticsearch instance or a cluser and a django app
2. create test view to create elasticsearch connection instance
 ```python
from elasticsearch import Elasticsearch
from django.http import HttpResponse
from django.views import View

class TestView(View):
    def get(self, request, *args, **kwargs):
        es = Elasticsearch(hosts=["elasticsearch"],sniff_on_start=True)
        return HttpResponse("Success !")
```
4. curl that view 

**Environment**
- OS:  debian buster docker
- Python version: 3.9
- Framework and version : django 3.2.15
- elasticserach-py: 7.17.14
- elasticsearch: 7.15.3
- APM Server version: 7.10.2
- Agent version: 6.10.1




--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `elasticapm/instrumentation/packages/elasticsearch.py`
Content:
```
1 #  BSD 3-Clause License
2 #
3 #  Copyright (c) 2019, Elasticsearch BV
4 #  All rights reserved.
5 #
6 #  Redistribution and use in source and binary forms, with or without
7 #  modification, are permitted provided that the following conditions are met:
8 #
9 #  * Redistributions of source code must retain the above copyright notice, this
10 #    list of conditions and the following disclaimer.
11 #
12 #  * Redistributions in binary form must reproduce the above copyright notice,
13 #    this list of conditions and the following disclaimer in the documentation
14 #    and/or other materials provided with the distribution.
15 #
16 #  * Neither the name of the copyright holder nor the names of its
17 #    contributors may be used to endorse or promote products derived from
18 #    this software without specific prior written permission.
19 #
20 #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 #  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 #  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 #  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 #  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 #  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 #  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 #  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 #  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 #  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 
31 from __future__ import absolute_import
32 
33 import re
34 
35 import elasticapm
36 from elasticapm.instrumentation.packages.base import AbstractInstrumentedModule
37 from elasticapm.traces import DroppedSpan, execution_context
38 from elasticapm.utils.logging import get_logger
39 
40 logger = get_logger("elasticapm.instrument")
41 
42 should_capture_body_re = re.compile("/(_search|_msearch|_count|_async_search|_sql|_eql)(/|$)")
43 
44 
45 class ElasticsearchConnectionInstrumentation(AbstractInstrumentedModule):
46     name = "elasticsearch_connection"
47 
48     instrument_list = [
49         ("elasticsearch.connection.http_urllib3", "Urllib3HttpConnection.perform_request"),
50         ("elasticsearch.connection.http_requests", "RequestsHttpConnection.perform_request"),
51     ]
52 
53     def call(self, module, method, wrapped, instance, args, kwargs):
54         span = execution_context.get_span()
55         if isinstance(span, DroppedSpan):
56             return wrapped(*args, **kwargs)
57 
58         self._update_context_by_request_data(span.context, instance, args, kwargs)
59 
60         status_code, headers, raw_data = wrapped(*args, **kwargs)
61 
62         span.context["http"] = {"status_code": status_code}
63 
64         return status_code, headers, raw_data
65 
66     def _update_context_by_request_data(self, context, instance, args, kwargs):
67         args_len = len(args)
68         url = args[1] if args_len > 1 else kwargs.get("url")
69         params = args[2] if args_len > 2 else kwargs.get("params")
70         body_serialized = args[3] if args_len > 3 else kwargs.get("body")
71 
72         should_capture_body = bool(should_capture_body_re.search(url))
73 
74         context["db"] = {"type": "elasticsearch"}
75         if should_capture_body:
76             query = []
77             # using both q AND body is allowed in some API endpoints / ES versions,
78             # but not in others. We simply capture both if they are there so the
79             # user can see it.
80             if params and "q" in params:
81                 # 'q' is already encoded to a byte string at this point
82                 # we assume utf8, which is the default
83                 query.append("q=" + params["q"].decode("utf-8", errors="replace"))
84             if body_serialized:
85                 if isinstance(body_serialized, bytes):
86                     query.append(body_serialized.decode("utf-8", errors="replace"))
87                 else:
88                     query.append(body_serialized)
89             if query:
90                 context["db"]["statement"] = "\n\n".join(query)
91 
92         context["destination"] = {
93             "address": instance.host,
94         }
95 
96 
97 class ElasticsearchTransportInstrumentation(AbstractInstrumentedModule):
98     name = "elasticsearch_connection"
99 
100     instrument_list = [
101         ("elasticsearch.transport", "Transport.perform_request"),
102     ]
103 
104     def call(self, module, method, wrapped, instance, args, kwargs):
105         with elasticapm.capture_span(
106             self._get_signature(args, kwargs),
107             span_type="db",
108             span_subtype="elasticsearch",
109             span_action="query",
110             extra={},
111             skip_frames=2,
112             leaf=True,
113         ) as span:
114             result_data = wrapped(*args, **kwargs)
115 
116             try:
117                 span.context["db"]["rows_affected"] = result_data["hits"]["total"]["value"]
118             except (KeyError, TypeError):
119                 pass
120 
121             return result_data
122 
123     def _get_signature(self, args, kwargs):
124         args_len = len(args)
125         http_method = args[0] if args_len else kwargs.get("method")
126         http_path = args[1] if args_len > 1 else kwargs.get("url")
127 
128         return "ES %s %s" % (http_method, http_path)
129
```
--- 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/elasticapm/instrumentation/packages/elasticsearch.py b/elasticapm/instrumentation/packages/elasticsearch.py
--- a/elasticapm/instrumentation/packages/elasticsearch.py
+++ b/elasticapm/instrumentation/packages/elasticsearch.py
@@ -52,7 +52,7 @@
 
     def call(self, module, method, wrapped, instance, args, kwargs):
         span = execution_context.get_span()
-        if isinstance(span, DroppedSpan):
+        if not span or isinstance(span, DroppedSpan):
             return wrapped(*args, **kwargs)
 
         self._update_context_by_request_data(span.context, instance, args, kwargs)

{"golden_diff": "diff --git a/elasticapm/instrumentation/packages/elasticsearch.py b/elasticapm/instrumentation/packages/elasticsearch.py\n--- a/elasticapm/instrumentation/packages/elasticsearch.py\n+++ b/elasticapm/instrumentation/packages/elasticsearch.py\n@@ -52,7 +52,7 @@\n \n     def call(self, module, method, wrapped, instance, args, kwargs):\n         span = execution_context.get_span()\n-        if isinstance(span, DroppedSpan):\n+        if not span or isinstance(span, DroppedSpan):\n             return wrapped(*args, **kwargs)\n \n         self._update_context_by_request_data(span.context, instance, args, kwargs)\n", "issue": "enabling sniff_on_start in elasticsearch() breaks elasticserach instrumentation\n**Describe the bug**:\r\nsniff_on_start in elasticsearch with apm enabled fails the request due to a null span object\r\n```python\r\nERROR File /opt/venv/lib/python3.9/site-packages/django/utils/log.py, line 224, Internal Server Error: /api/test_view\r\nTraceback (most recent call last):\r\n  File \"/opt/venv/lib/python3.9/site-packages/django/core/handlers/exception.py\", line 47, in inner\r\n    response = get_response(request)\r\n  File \"/opt/venv/lib/python3.9/site-packages/django/core/handlers/base.py\", line 181, in _get_response\r\n    response = wrapped_callback(request, *callback_args, **callback_kwargs)\r\n  File \"/usr/local/lib/python3.9/contextlib.py\", line 79, in inner\r\n    return func(*args, **kwds)\r\n  File \"/opt/venv/lib/python3.9/site-packages/django/views/generic/base.py\", line 70, in view\r\n    return self.dispatch(request, *args, **kwargs)\r\n  File \"/opt/venv/lib/python3.9/site-packages/django/views/generic/base.py\", line 98, in dispatch\r\n    return handler(request, *args, **kwargs)\r\n  File \"/app/api/views.py\", line 592, in get\r\n    es = Elasticsearch(hosts=[\"elasticsearch\"],sniff_on_start=True)\r\n  File \"/opt/venv/lib/python3.9/site-packages/elasticsearch/client/__init__.py\", line 205, in __init__\r\n    self.transport = transport_class(_normalize_hosts(hosts), **kwargs)\r\n  File \"/opt/venv/lib/python3.9/site-packages/elasticsearch/transport.py\", line 200, in __init__\r\n    self.sniff_hosts(True)\r\n  File \"/opt/venv/lib/python3.9/site-packages/elasticsearch/transport.py\", line 365, in sniff_hosts\r\n    node_info = self._get_sniff_data(initial)\r\n  File \"/opt/venv/lib/python3.9/site-packages/elasticsearch/transport.py\", line 308, in _get_sniff_data\r\n    _, headers, node_info = c.perform_request(\r\n  File \"/opt/venv/lib/python3.9/site-packages/elasticapm/instrumentation/packages/base.py\", line 210, in call_if_sampling\r\n    return self.call(module, method, wrapped, instance, args, kwargs)\r\n  File \"/opt/venv/lib/python3.9/site-packages/elasticapm/instrumentation/packages/elasticsearch.py\", line 58, in call\r\n    self._update_context_by_request_data(span.context, instance, args, kwargs)\r\nAttributeError: 'NoneType' object has no attribute 'context'\r\n\r\n```\r\n\r\n\r\n**To Reproduce**\r\n\r\n1. setup an elasticsearch instance or a cluser and a django app\r\n2. create test view to create elasticsearch connection instance\r\n ```python\r\nfrom elasticsearch import Elasticsearch\r\nfrom django.http import HttpResponse\r\nfrom django.views import View\r\n\r\nclass TestView(View):\r\n    def get(self, request, *args, **kwargs):\r\n        es = Elasticsearch(hosts=[\"elasticsearch\"],sniff_on_start=True)\r\n        return HttpResponse(\"Success !\")\r\n```\r\n4. curl that view \r\n\r\n**Environment**\r\n- OS:  debian buster docker\r\n- Python version: 3.9\r\n- Framework and version : django 3.2.15\r\n- elasticserach-py: 7.17.14\r\n- elasticsearch: 7.15.3\r\n- APM Server version: 7.10.2\r\n- Agent version: 6.10.1\r\n\r\n\r\n\n", "before_files": [{"content": "#  BSD 3-Clause License\n#\n#  Copyright (c) 2019, Elasticsearch BV\n#  All rights reserved.\n#\n#  Redistribution and use in source and binary forms, with or without\n#  modification, are permitted provided that the following conditions are met:\n#\n#  * Redistributions of source code must retain the above copyright notice, this\n#    list of conditions and the following disclaimer.\n#\n#  * Redistributions in binary form must reproduce the above copyright notice,\n#    this list of conditions and the following disclaimer in the documentation\n#    and/or other materials provided with the distribution.\n#\n#  * Neither the name of the copyright holder nor the names of its\n#    contributors may be used to endorse or promote products derived from\n#    this software without specific prior written permission.\n#\n#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n#  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n#  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n#  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n#  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n#  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nfrom __future__ import absolute_import\n\nimport re\n\nimport elasticapm\nfrom elasticapm.instrumentation.packages.base import AbstractInstrumentedModule\nfrom elasticapm.traces import DroppedSpan, execution_context\nfrom elasticapm.utils.logging import get_logger\n\nlogger = get_logger(\"elasticapm.instrument\")\n\nshould_capture_body_re = re.compile(\"/(_search|_msearch|_count|_async_search|_sql|_eql)(/|$)\")\n\n\nclass ElasticsearchConnectionInstrumentation(AbstractInstrumentedModule):\n    name = \"elasticsearch_connection\"\n\n    instrument_list = [\n        (\"elasticsearch.connection.http_urllib3\", \"Urllib3HttpConnection.perform_request\"),\n        (\"elasticsearch.connection.http_requests\", \"RequestsHttpConnection.perform_request\"),\n    ]\n\n    def call(self, module, method, wrapped, instance, args, kwargs):\n        span = execution_context.get_span()\n        if isinstance(span, DroppedSpan):\n            return wrapped(*args, **kwargs)\n\n        self._update_context_by_request_data(span.context, instance, args, kwargs)\n\n        status_code, headers, raw_data = wrapped(*args, **kwargs)\n\n        span.context[\"http\"] = {\"status_code\": status_code}\n\n        return status_code, headers, raw_data\n\n    def _update_context_by_request_data(self, context, instance, args, kwargs):\n        args_len = len(args)\n        url = args[1] if args_len > 1 else kwargs.get(\"url\")\n        params = args[2] if args_len > 2 else kwargs.get(\"params\")\n        body_serialized = args[3] if args_len > 3 else kwargs.get(\"body\")\n\n        should_capture_body = bool(should_capture_body_re.search(url))\n\n        context[\"db\"] = {\"type\": \"elasticsearch\"}\n        if should_capture_body:\n            query = []\n            # using both q AND body is allowed in some API endpoints / ES versions,\n            # but not in others. We simply capture both if they are there so the\n            # user can see it.\n            if params and \"q\" in params:\n                # 'q' is already encoded to a byte string at this point\n                # we assume utf8, which is the default\n                query.append(\"q=\" + params[\"q\"].decode(\"utf-8\", errors=\"replace\"))\n            if body_serialized:\n                if isinstance(body_serialized, bytes):\n                    query.append(body_serialized.decode(\"utf-8\", errors=\"replace\"))\n                else:\n                    query.append(body_serialized)\n            if query:\n                context[\"db\"][\"statement\"] = \"\\n\\n\".join(query)\n\n        context[\"destination\"] = {\n            \"address\": instance.host,\n        }\n\n\nclass ElasticsearchTransportInstrumentation(AbstractInstrumentedModule):\n    name = \"elasticsearch_connection\"\n\n    instrument_list = [\n        (\"elasticsearch.transport\", \"Transport.perform_request\"),\n    ]\n\n    def call(self, module, method, wrapped, instance, args, kwargs):\n        with elasticapm.capture_span(\n            self._get_signature(args, kwargs),\n            span_type=\"db\",\n            span_subtype=\"elasticsearch\",\n            span_action=\"query\",\n            extra={},\n            skip_frames=2,\n            leaf=True,\n        ) as span:\n            result_data = wrapped(*args, **kwargs)\n\n            try:\n                span.context[\"db\"][\"rows_affected\"] = result_data[\"hits\"][\"total\"][\"value\"]\n            except (KeyError, TypeError):\n                pass\n\n            return result_data\n\n    def _get_signature(self, args, kwargs):\n        args_len = len(args)\n        http_method = args[0] if args_len else kwargs.get(\"method\")\n        http_path = args[1] if args_len > 1 else kwargs.get(\"url\")\n\n        return \"ES %s %s\" % (http_method, http_path)\n", "path": "elasticapm/instrumentation/packages/elasticsearch.py"}], "after_files": [{"content": "#  BSD 3-Clause License\n#\n#  Copyright (c) 2019, Elasticsearch BV\n#  All rights reserved.\n#\n#  Redistribution and use in source and binary forms, with or without\n#  modification, are permitted provided that the following conditions are met:\n#\n#  * Redistributions of source code must retain the above copyright notice, this\n#    list of conditions and the following disclaimer.\n#\n#  * Redistributions in binary form must reproduce the above copyright notice,\n#    this list of conditions and the following disclaimer in the documentation\n#    and/or other materials provided with the distribution.\n#\n#  * Neither the name of the copyright holder nor the names of its\n#    contributors may be used to endorse or promote products derived from\n#    this software without specific prior written permission.\n#\n#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n#  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n#  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n#  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n#  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n#  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nfrom __future__ import absolute_import\n\nimport re\n\nimport elasticapm\nfrom elasticapm.instrumentation.packages.base import AbstractInstrumentedModule\nfrom elasticapm.traces import DroppedSpan, execution_context\nfrom elasticapm.utils.logging import get_logger\n\nlogger = get_logger(\"elasticapm.instrument\")\n\nshould_capture_body_re = re.compile(\"/(_search|_msearch|_count|_async_search|_sql|_eql)(/|$)\")\n\n\nclass ElasticsearchConnectionInstrumentation(AbstractInstrumentedModule):\n    name = \"elasticsearch_connection\"\n\n    instrument_list = [\n        (\"elasticsearch.connection.http_urllib3\", \"Urllib3HttpConnection.perform_request\"),\n        (\"elasticsearch.connection.http_requests\", \"RequestsHttpConnection.perform_request\"),\n    ]\n\n    def call(self, module, method, wrapped, instance, args, kwargs):\n        span = execution_context.get_span()\n        if not span or isinstance(span, DroppedSpan):\n            return wrapped(*args, **kwargs)\n\n        self._update_context_by_request_data(span.context, instance, args, kwargs)\n\n        status_code, headers, raw_data = wrapped(*args, **kwargs)\n\n        span.context[\"http\"] = {\"status_code\": status_code}\n\n        return status_code, headers, raw_data\n\n    def _update_context_by_request_data(self, context, instance, args, kwargs):\n        args_len = len(args)\n        url = args[1] if args_len > 1 else kwargs.get(\"url\")\n        params = args[2] if args_len > 2 else kwargs.get(\"params\")\n        body_serialized = args[3] if args_len > 3 else kwargs.get(\"body\")\n\n        should_capture_body = bool(should_capture_body_re.search(url))\n\n        context[\"db\"] = {\"type\": \"elasticsearch\"}\n        if should_capture_body:\n            query = []\n            # using both q AND body is allowed in some API endpoints / ES versions,\n            # but not in others. We simply capture both if they are there so the\n            # user can see it.\n            if params and \"q\" in params:\n                # 'q' is already encoded to a byte string at this point\n                # we assume utf8, which is the default\n                query.append(\"q=\" + params[\"q\"].decode(\"utf-8\", errors=\"replace\"))\n            if body_serialized:\n                if isinstance(body_serialized, bytes):\n                    query.append(body_serialized.decode(\"utf-8\", errors=\"replace\"))\n                else:\n                    query.append(body_serialized)\n            if query:\n                context[\"db\"][\"statement\"] = \"\\n\\n\".join(query)\n\n        context[\"destination\"] = {\n            \"address\": instance.host,\n        }\n\n\nclass ElasticsearchTransportInstrumentation(AbstractInstrumentedModule):\n    name = \"elasticsearch_connection\"\n\n    instrument_list = [\n        (\"elasticsearch.transport\", \"Transport.perform_request\"),\n    ]\n\n    def call(self, module, method, wrapped, instance, args, kwargs):\n        with elasticapm.capture_span(\n            self._get_signature(args, kwargs),\n            span_type=\"db\",\n            span_subtype=\"elasticsearch\",\n            span_action=\"query\",\n            extra={},\n            skip_frames=2,\n            leaf=True,\n        ) as span:\n            result_data = wrapped(*args, **kwargs)\n\n            try:\n                span.context[\"db\"][\"rows_affected\"] = result_data[\"hits\"][\"total\"][\"value\"]\n            except (KeyError, TypeError):\n                pass\n\n            return result_data\n\n    def _get_signature(self, args, kwargs):\n        args_len = len(args)\n        http_method = args[0] if args_len else kwargs.get(\"method\")\n        http_path = args[1] if args_len > 1 else kwargs.get(\"url\")\n\n        return \"ES %s %s\" % (http_method, http_path)\n", "path": "elasticapm/instrumentation/packages/elasticsearch.py"}]}