We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
IndexError when "url" not found in args or kwargs.
### Overview

I've found an issue in line 46 of `call()` method in `AioHttpClientInstrumentation(...)` class.

https://github.com/elastic/apm-agent-python/blob/da93e7af448abcac367d216e2d20a584051f6e50/elasticapm/instrumentation/packages/asyncio/aiohttp_client.py#L44-L47

I'm getting an `IndexError` exception due to lack or "url" in both kwargs and in args[1]. The the reason is that the argument containing urls is called "str_or_url".

https://github.com/aio-libs/aiohttp/blob/4b59d55e9e79f5a0b1932d6dc9f6b12a33d19266/aiohttp/client.py#L325-L328

By default the code is running fine, but this issue will appear in cases where someone will try to use `ClientSession._request()` method directly AND use keyword-arguments.

### How to recreate the bug?

This is a general example on how to recreate the bug. Lets assume that somewhere in my code I want to connect to some external http rest-api service using aiohttp library. I'll be using custom made session object based on ClientSession object from aiohttp library.

```python
from aiohttp import ClientSession, ClientResponse

class CustomSession(ClientSession):

    async def _request(self, method: str, str_or_url: StrOrURL, **kwargs: Any) -> ClientResponse:
        # put some extra code here, like add retry functionality
        client_response = await super()._request(method=method, str_or_url=url, **kwargs) # << ISSUE IS HERE
        return client_response
```
the above code is valid and it works as long as there is no apm agent running as middleware. With apm agent added, the code has to be written as below:
```python
        client_response = await super()._request(method, url, **kwargs)
```

### How to fix it?

Do search for both `url` and `str_or_url` in kwargs. It's simple fix that can fit in same line, I bet.

--- 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/asyncio/aiohttp_client.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 elasticapm import async_capture_span
32 from elasticapm.conf import constants
33 from elasticapm.instrumentation.packages.asyncio.base import AsyncAbstractInstrumentedModule
34 from elasticapm.traces import DroppedSpan, execution_context
35 from elasticapm.utils import get_host_from_url, sanitize_url
36 from elasticapm.utils.disttracing import TracingOptions
37 
38 
39 class AioHttpClientInstrumentation(AsyncAbstractInstrumentedModule):
40     name = "aiohttp_client"
41 
42     instrument_list = [("aiohttp.client", "ClientSession._request")]
43 
44     async def call(self, module, method, wrapped, instance, args, kwargs):
45         method = kwargs["method"] if "method" in kwargs else args[0]
46         url = kwargs["url"] if "url" in kwargs else args[1]
47         url = str(url)
48 
49         signature = " ".join([method.upper(), get_host_from_url(url)])
50         url = sanitize_url(url)
51         transaction = execution_context.get_transaction()
52 
53         async with async_capture_span(
54             signature,
55             span_type="external",
56             span_subtype="http",
57             extra={"http": {"url": url}},
58             leaf=True,
59         ) as span:
60             leaf_span = span
61             while isinstance(leaf_span, DroppedSpan):
62                 leaf_span = leaf_span.parent
63 
64             parent_id = leaf_span.id if leaf_span else transaction.id
65             trace_parent = transaction.trace_parent.copy_from(
66                 span_id=parent_id, trace_options=TracingOptions(recorded=True)
67             )
68             headers = kwargs.get("headers") or {}
69             self._set_disttracing_headers(headers, trace_parent, transaction)
70             kwargs["headers"] = headers
71             response = await wrapped(*args, **kwargs)
72             if response:
73                 if span.context:
74                     span.context["http"]["status_code"] = response.status
75                 span.set_success() if response.status < 400 else span.set_failure()
76             return response
77 
78     def mutate_unsampled_call_args(self, module, method, wrapped, instance, args, kwargs, transaction):
79         # since we don't have a span, we set the span id to the transaction id
80         trace_parent = transaction.trace_parent.copy_from(
81             span_id=transaction.id, trace_options=TracingOptions(recorded=False)
82         )
83 
84         headers = kwargs.get("headers") or {}
85         self._set_disttracing_headers(headers, trace_parent, transaction)
86         kwargs["headers"] = headers
87         return args, kwargs
88 
89     def _set_disttracing_headers(self, headers, trace_parent, transaction):
90         trace_parent_str = trace_parent.to_string()
91         headers[constants.TRACEPARENT_HEADER_NAME] = trace_parent_str
92         if transaction.tracer.config.use_elastic_traceparent_header:
93             headers[constants.TRACEPARENT_LEGACY_HEADER_NAME] = trace_parent_str
94         if trace_parent.tracestate:
95             headers[constants.TRACESTATE_HEADER_NAME] = trace_parent.tracestate
96
```
--- 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/asyncio/aiohttp_client.py b/elasticapm/instrumentation/packages/asyncio/aiohttp_client.py
--- a/elasticapm/instrumentation/packages/asyncio/aiohttp_client.py
+++ b/elasticapm/instrumentation/packages/asyncio/aiohttp_client.py
@@ -42,8 +42,10 @@
     instrument_list = [("aiohttp.client", "ClientSession._request")]
 
     async def call(self, module, method, wrapped, instance, args, kwargs):
-        method = kwargs["method"] if "method" in kwargs else args[0]
-        url = kwargs["url"] if "url" in kwargs else args[1]
+        method = kwargs.get("method", args[0])
+        url = kwargs.get("url", kwargs.get("str_or_url", None))
+        if url is None:
+            url = args[1]
         url = str(url)
 
         signature = " ".join([method.upper(), get_host_from_url(url)])

{"golden_diff": "diff --git a/elasticapm/instrumentation/packages/asyncio/aiohttp_client.py b/elasticapm/instrumentation/packages/asyncio/aiohttp_client.py\n--- a/elasticapm/instrumentation/packages/asyncio/aiohttp_client.py\n+++ b/elasticapm/instrumentation/packages/asyncio/aiohttp_client.py\n@@ -42,8 +42,10 @@\n     instrument_list = [(\"aiohttp.client\", \"ClientSession._request\")]\n \n     async def call(self, module, method, wrapped, instance, args, kwargs):\n-        method = kwargs[\"method\"] if \"method\" in kwargs else args[0]\n-        url = kwargs[\"url\"] if \"url\" in kwargs else args[1]\n+        method = kwargs.get(\"method\", args[0])\n+        url = kwargs.get(\"url\", kwargs.get(\"str_or_url\", None))\n+        if url is None:\n+            url = args[1]\n         url = str(url)\n \n         signature = \" \".join([method.upper(), get_host_from_url(url)])\n", "issue": "IndexError when \"url\" not found in args or kwargs.\n### Overview\r\n\r\nI've found an issue in line 46 of `call()` method in `AioHttpClientInstrumentation(...)` class.\r\n\r\nhttps://github.com/elastic/apm-agent-python/blob/da93e7af448abcac367d216e2d20a584051f6e50/elasticapm/instrumentation/packages/asyncio/aiohttp_client.py#L44-L47\r\n\r\nI'm getting an `IndexError` exception due to lack or \"url\" in both kwargs and in args[1]. The the reason is that the argument containing urls is called \"str_or_url\".\r\n\r\nhttps://github.com/aio-libs/aiohttp/blob/4b59d55e9e79f5a0b1932d6dc9f6b12a33d19266/aiohttp/client.py#L325-L328\r\n\r\nBy default the code is running fine, but this issue will appear in cases where someone will try to use `ClientSession._request()` method directly AND use keyword-arguments.\r\n\r\n### How to recreate the bug?\r\n\r\nThis is a general example on how to recreate the bug. Lets assume that somewhere in my code I want to connect to some external http rest-api service using aiohttp library. I'll be using custom made session object based on ClientSession object from aiohttp library.\r\n\r\n```python\r\nfrom aiohttp import ClientSession, ClientResponse\r\n\r\nclass CustomSession(ClientSession):\r\n\r\n    async def _request(self, method: str, str_or_url: StrOrURL, **kwargs: Any) -> ClientResponse:\r\n        # put some extra code here, like add retry functionality\r\n        client_response = await super()._request(method=method, str_or_url=url, **kwargs) # << ISSUE IS HERE\r\n        return client_response\r\n```\r\nthe above code is valid and it works as long as there is no apm agent running as middleware. With apm agent added, the code has to be written as below:\r\n```python\r\n        client_response = await super()._request(method, url, **kwargs)\r\n```\r\n\r\n### How to fix it?\r\n\r\nDo search for both `url` and `str_or_url` in kwargs. It's simple fix that can fit in same line, I bet.\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 elasticapm import async_capture_span\nfrom elasticapm.conf import constants\nfrom elasticapm.instrumentation.packages.asyncio.base import AsyncAbstractInstrumentedModule\nfrom elasticapm.traces import DroppedSpan, execution_context\nfrom elasticapm.utils import get_host_from_url, sanitize_url\nfrom elasticapm.utils.disttracing import TracingOptions\n\n\nclass AioHttpClientInstrumentation(AsyncAbstractInstrumentedModule):\n    name = \"aiohttp_client\"\n\n    instrument_list = [(\"aiohttp.client\", \"ClientSession._request\")]\n\n    async def call(self, module, method, wrapped, instance, args, kwargs):\n        method = kwargs[\"method\"] if \"method\" in kwargs else args[0]\n        url = kwargs[\"url\"] if \"url\" in kwargs else args[1]\n        url = str(url)\n\n        signature = \" \".join([method.upper(), get_host_from_url(url)])\n        url = sanitize_url(url)\n        transaction = execution_context.get_transaction()\n\n        async with async_capture_span(\n            signature,\n            span_type=\"external\",\n            span_subtype=\"http\",\n            extra={\"http\": {\"url\": url}},\n            leaf=True,\n        ) as span:\n            leaf_span = span\n            while isinstance(leaf_span, DroppedSpan):\n                leaf_span = leaf_span.parent\n\n            parent_id = leaf_span.id if leaf_span else transaction.id\n            trace_parent = transaction.trace_parent.copy_from(\n                span_id=parent_id, trace_options=TracingOptions(recorded=True)\n            )\n            headers = kwargs.get(\"headers\") or {}\n            self._set_disttracing_headers(headers, trace_parent, transaction)\n            kwargs[\"headers\"] = headers\n            response = await wrapped(*args, **kwargs)\n            if response:\n                if span.context:\n                    span.context[\"http\"][\"status_code\"] = response.status\n                span.set_success() if response.status < 400 else span.set_failure()\n            return response\n\n    def mutate_unsampled_call_args(self, module, method, wrapped, instance, args, kwargs, transaction):\n        # since we don't have a span, we set the span id to the transaction id\n        trace_parent = transaction.trace_parent.copy_from(\n            span_id=transaction.id, trace_options=TracingOptions(recorded=False)\n        )\n\n        headers = kwargs.get(\"headers\") or {}\n        self._set_disttracing_headers(headers, trace_parent, transaction)\n        kwargs[\"headers\"] = headers\n        return args, kwargs\n\n    def _set_disttracing_headers(self, headers, trace_parent, transaction):\n        trace_parent_str = trace_parent.to_string()\n        headers[constants.TRACEPARENT_HEADER_NAME] = trace_parent_str\n        if transaction.tracer.config.use_elastic_traceparent_header:\n            headers[constants.TRACEPARENT_LEGACY_HEADER_NAME] = trace_parent_str\n        if trace_parent.tracestate:\n            headers[constants.TRACESTATE_HEADER_NAME] = trace_parent.tracestate\n", "path": "elasticapm/instrumentation/packages/asyncio/aiohttp_client.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 elasticapm import async_capture_span\nfrom elasticapm.conf import constants\nfrom elasticapm.instrumentation.packages.asyncio.base import AsyncAbstractInstrumentedModule\nfrom elasticapm.traces import DroppedSpan, execution_context\nfrom elasticapm.utils import get_host_from_url, sanitize_url\nfrom elasticapm.utils.disttracing import TracingOptions\n\n\nclass AioHttpClientInstrumentation(AsyncAbstractInstrumentedModule):\n    name = \"aiohttp_client\"\n\n    instrument_list = [(\"aiohttp.client\", \"ClientSession._request\")]\n\n    async def call(self, module, method, wrapped, instance, args, kwargs):\n        method = kwargs.get(\"method\", args[0])\n        url = kwargs.get(\"url\", kwargs.get(\"str_or_url\", None))\n        if url is None:\n            url = args[1]\n        url = str(url)\n\n        signature = \" \".join([method.upper(), get_host_from_url(url)])\n        url = sanitize_url(url)\n        transaction = execution_context.get_transaction()\n\n        async with async_capture_span(\n            signature,\n            span_type=\"external\",\n            span_subtype=\"http\",\n            extra={\"http\": {\"url\": url}},\n            leaf=True,\n        ) as span:\n            leaf_span = span\n            while isinstance(leaf_span, DroppedSpan):\n                leaf_span = leaf_span.parent\n\n            parent_id = leaf_span.id if leaf_span else transaction.id\n            trace_parent = transaction.trace_parent.copy_from(\n                span_id=parent_id, trace_options=TracingOptions(recorded=True)\n            )\n            headers = kwargs.get(\"headers\") or {}\n            self._set_disttracing_headers(headers, trace_parent, transaction)\n            kwargs[\"headers\"] = headers\n            response = await wrapped(*args, **kwargs)\n            if response:\n                if span.context:\n                    span.context[\"http\"][\"status_code\"] = response.status\n                span.set_success() if response.status < 400 else span.set_failure()\n            return response\n\n    def mutate_unsampled_call_args(self, module, method, wrapped, instance, args, kwargs, transaction):\n        # since we don't have a span, we set the span id to the transaction id\n        trace_parent = transaction.trace_parent.copy_from(\n            span_id=transaction.id, trace_options=TracingOptions(recorded=False)\n        )\n\n        headers = kwargs.get(\"headers\") or {}\n        self._set_disttracing_headers(headers, trace_parent, transaction)\n        kwargs[\"headers\"] = headers\n        return args, kwargs\n\n    def _set_disttracing_headers(self, headers, trace_parent, transaction):\n        trace_parent_str = trace_parent.to_string()\n        headers[constants.TRACEPARENT_HEADER_NAME] = trace_parent_str\n        if transaction.tracer.config.use_elastic_traceparent_header:\n            headers[constants.TRACEPARENT_LEGACY_HEADER_NAME] = trace_parent_str\n        if trace_parent.tracestate:\n            headers[constants.TRACESTATE_HEADER_NAME] = trace_parent.tracestate\n", "path": "elasticapm/instrumentation/packages/asyncio/aiohttp_client.py"}]}