prompt: 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()
  
  ```
  
golden_diff: 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)])
  
verification_info: {"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"}]}