We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Fix asgi instrumentation default span name
Similar to [wsgi](https://github.com/open-telemetry/opentelemetry-python/blob/master/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py#L171), the default span name for SERVER spans should be `HTTP <method>` as outlined by the [specs](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#name).

Part of https://github.com/open-telemetry/opentelemetry-python-contrib/issues/434

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py`
Content:
```
1 # Copyright The OpenTelemetry Authors
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 """
16 The opentelemetry-instrumentation-asgi package provides an ASGI middleware that can be used
17 on any ASGI framework (such as Django-channels / Quart) to track requests
18 timing through OpenTelemetry.
19 """
20 
21 import typing
22 import urllib
23 from functools import wraps
24 from typing import Tuple
25 
26 from asgiref.compatibility import guarantee_single_callable
27 
28 from opentelemetry import context, trace
29 from opentelemetry.instrumentation.asgi.version import __version__  # noqa
30 from opentelemetry.instrumentation.utils import http_status_to_status_code
31 from opentelemetry.propagate import extract
32 from opentelemetry.propagators.textmap import Getter
33 from opentelemetry.semconv.trace import SpanAttributes
34 from opentelemetry.trace.status import Status, StatusCode
35 
36 
37 class ASGIGetter(Getter):
38     def get(
39         self, carrier: dict, key: str
40     ) -> typing.Optional[typing.List[str]]:
41         """Getter implementation to retrieve a HTTP header value from the ASGI
42         scope.
43 
44         Args:
45             carrier: ASGI scope object
46             key: header name in scope
47         Returns:
48             A list with a single string with the header value if it exists,
49                 else None.
50         """
51         headers = carrier.get("headers")
52         if not headers:
53             return None
54 
55         # asgi header keys are in lower case
56         key = key.lower()
57         decoded = [
58             _value.decode("utf8")
59             for (_key, _value) in headers
60             if _key.decode("utf8") == key
61         ]
62         if not decoded:
63             return None
64         return decoded
65 
66     def keys(self, carrier: dict) -> typing.List[str]:
67         return list(carrier.keys())
68 
69 
70 asgi_getter = ASGIGetter()
71 
72 
73 def collect_request_attributes(scope):
74     """Collects HTTP request attributes from the ASGI scope and returns a
75     dictionary to be used as span creation attributes."""
76     server_host, port, http_url = get_host_port_url_tuple(scope)
77     query_string = scope.get("query_string")
78     if query_string and http_url:
79         if isinstance(query_string, bytes):
80             query_string = query_string.decode("utf8")
81         http_url = http_url + ("?" + urllib.parse.unquote(query_string))
82 
83     result = {
84         SpanAttributes.HTTP_SCHEME: scope.get("scheme"),
85         SpanAttributes.HTTP_HOST: server_host,
86         SpanAttributes.NET_HOST_PORT: port,
87         SpanAttributes.HTTP_FLAVOR: scope.get("http_version"),
88         SpanAttributes.HTTP_TARGET: scope.get("path"),
89         SpanAttributes.HTTP_URL: http_url,
90     }
91     http_method = scope.get("method")
92     if http_method:
93         result[SpanAttributes.HTTP_METHOD] = http_method
94 
95     http_host_value_list = asgi_getter.get(scope, "host")
96     if http_host_value_list:
97         result[SpanAttributes.HTTP_SERVER_NAME] = ",".join(
98             http_host_value_list
99         )
100     http_user_agent = asgi_getter.get(scope, "user-agent")
101     if http_user_agent:
102         result[SpanAttributes.HTTP_USER_AGENT] = http_user_agent[0]
103 
104     if "client" in scope and scope["client"] is not None:
105         result[SpanAttributes.NET_PEER_IP] = scope.get("client")[0]
106         result[SpanAttributes.NET_PEER_PORT] = scope.get("client")[1]
107 
108     # remove None values
109     result = {k: v for k, v in result.items() if v is not None}
110 
111     return result
112 
113 
114 def get_host_port_url_tuple(scope):
115     """Returns (host, port, full_url) tuple."""
116     server = scope.get("server") or ["0.0.0.0", 80]
117     port = server[1]
118     server_host = server[0] + (":" + str(port) if port != 80 else "")
119     full_path = scope.get("root_path", "") + scope.get("path", "")
120     http_url = scope.get("scheme", "http") + "://" + server_host + full_path
121     return server_host, port, http_url
122 
123 
124 def set_status_code(span, status_code):
125     """Adds HTTP response attributes to span using the status_code argument."""
126     if not span.is_recording():
127         return
128     try:
129         status_code = int(status_code)
130     except ValueError:
131         span.set_status(
132             Status(
133                 StatusCode.ERROR,
134                 "Non-integer HTTP status: " + repr(status_code),
135             )
136         )
137     else:
138         span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)
139         span.set_status(Status(http_status_to_status_code(status_code)))
140 
141 
142 def get_default_span_details(scope: dict) -> Tuple[str, dict]:
143     """Default implementation for span_details_callback
144 
145     Args:
146         scope: the asgi scope dictionary
147 
148     Returns:
149         a tuple of the span, and any attributes to attach to the
150         span.
151     """
152     method_or_path = scope.get("method") or scope.get("path")
153 
154     return method_or_path, {}
155 
156 
157 class OpenTelemetryMiddleware:
158     """The ASGI application middleware.
159 
160     This class is an ASGI middleware that starts and annotates spans for any
161     requests it is invoked with.
162 
163     Args:
164         app: The ASGI application callable to forward requests to.
165         span_details_callback: Callback which should return a string
166             and a tuple, representing the desired span name and a
167             dictionary with any additional span attributes to set.
168             Optional: Defaults to get_default_span_details.
169         tracer_provider: The optional tracer provider to use. If omitted
170             the current globally configured one is used.
171     """
172 
173     def __init__(
174         self,
175         app,
176         excluded_urls=None,
177         span_details_callback=None,
178         tracer_provider=None,
179     ):
180         self.app = guarantee_single_callable(app)
181         self.tracer = trace.get_tracer(__name__, __version__, tracer_provider)
182         self.span_details_callback = (
183             span_details_callback or get_default_span_details
184         )
185         self.excluded_urls = excluded_urls
186 
187     async def __call__(self, scope, receive, send):
188         """The ASGI application
189 
190         Args:
191             scope: A ASGI environment.
192             receive: An awaitable callable yielding dictionaries
193             send: An awaitable callable taking a single dictionary as argument.
194         """
195         if scope["type"] not in ("http", "websocket"):
196             return await self.app(scope, receive, send)
197 
198         _, _, url = get_host_port_url_tuple(scope)
199         if self.excluded_urls and self.excluded_urls.url_disabled(url):
200             return await self.app(scope, receive, send)
201 
202         token = context.attach(extract(scope, getter=asgi_getter))
203         span_name, additional_attributes = self.span_details_callback(scope)
204 
205         try:
206             with self.tracer.start_as_current_span(
207                 span_name + " asgi", kind=trace.SpanKind.SERVER,
208             ) as span:
209                 if span.is_recording():
210                     attributes = collect_request_attributes(scope)
211                     attributes.update(additional_attributes)
212                     for key, value in attributes.items():
213                         span.set_attribute(key, value)
214 
215                 @wraps(receive)
216                 async def wrapped_receive():
217                     with self.tracer.start_as_current_span(
218                         span_name + " asgi." + scope["type"] + ".receive"
219                     ) as receive_span:
220                         message = await receive()
221                         if receive_span.is_recording():
222                             if message["type"] == "websocket.receive":
223                                 set_status_code(receive_span, 200)
224                             receive_span.set_attribute("type", message["type"])
225                     return message
226 
227                 @wraps(send)
228                 async def wrapped_send(message):
229                     with self.tracer.start_as_current_span(
230                         span_name + " asgi." + scope["type"] + ".send"
231                     ) as send_span:
232                         if send_span.is_recording():
233                             if message["type"] == "http.response.start":
234                                 status_code = message["status"]
235                                 set_status_code(span, status_code)
236                                 set_status_code(send_span, status_code)
237                             elif message["type"] == "websocket.send":
238                                 set_status_code(span, 200)
239                                 set_status_code(send_span, 200)
240                             send_span.set_attribute("type", message["type"])
241                         await send(message)
242 
243                 await self.app(scope, wrapped_receive, wrapped_send)
244         finally:
245             context.detach(token)
246
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py
--- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py
+++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py
@@ -146,12 +146,13 @@
         scope: the asgi scope dictionary
 
     Returns:
-        a tuple of the span, and any attributes to attach to the
-        span.
+        a tuple of the span name, and any attributes to attach to the span.
     """
-    method_or_path = scope.get("method") or scope.get("path")
+    span_name = scope.get("path", "").strip() or "HTTP {}".format(
+        scope.get("method", "").strip()
+    )
 
-    return method_or_path, {}
+    return span_name, {}
 
 
 class OpenTelemetryMiddleware:
@@ -204,7 +205,7 @@
 
         try:
             with self.tracer.start_as_current_span(
-                span_name + " asgi", kind=trace.SpanKind.SERVER,
+                span_name, kind=trace.SpanKind.SERVER,
             ) as span:
                 if span.is_recording():
                     attributes = collect_request_attributes(scope)
@@ -215,7 +216,7 @@
                 @wraps(receive)
                 async def wrapped_receive():
                     with self.tracer.start_as_current_span(
-                        span_name + " asgi." + scope["type"] + ".receive"
+                        " ".join((span_name, scope["type"], "receive"))
                     ) as receive_span:
                         message = await receive()
                         if receive_span.is_recording():
@@ -227,7 +228,7 @@
                 @wraps(send)
                 async def wrapped_send(message):
                     with self.tracer.start_as_current_span(
-                        span_name + " asgi." + scope["type"] + ".send"
+                        " ".join((span_name, scope["type"], "send"))
                     ) as send_span:
                         if send_span.is_recording():
                             if message["type"] == "http.response.start":

{"golden_diff": "diff --git a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py\n--- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py\n+++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py\n@@ -146,12 +146,13 @@\n         scope: the asgi scope dictionary\n \n     Returns:\n-        a tuple of the span, and any attributes to attach to the\n-        span.\n+        a tuple of the span name, and any attributes to attach to the span.\n     \"\"\"\n-    method_or_path = scope.get(\"method\") or scope.get(\"path\")\n+    span_name = scope.get(\"path\", \"\").strip() or \"HTTP {}\".format(\n+        scope.get(\"method\", \"\").strip()\n+    )\n \n-    return method_or_path, {}\n+    return span_name, {}\n \n \n class OpenTelemetryMiddleware:\n@@ -204,7 +205,7 @@\n \n         try:\n             with self.tracer.start_as_current_span(\n-                span_name + \" asgi\", kind=trace.SpanKind.SERVER,\n+                span_name, kind=trace.SpanKind.SERVER,\n             ) as span:\n                 if span.is_recording():\n                     attributes = collect_request_attributes(scope)\n@@ -215,7 +216,7 @@\n                 @wraps(receive)\n                 async def wrapped_receive():\n                     with self.tracer.start_as_current_span(\n-                        span_name + \" asgi.\" + scope[\"type\"] + \".receive\"\n+                        \" \".join((span_name, scope[\"type\"], \"receive\"))\n                     ) as receive_span:\n                         message = await receive()\n                         if receive_span.is_recording():\n@@ -227,7 +228,7 @@\n                 @wraps(send)\n                 async def wrapped_send(message):\n                     with self.tracer.start_as_current_span(\n-                        span_name + \" asgi.\" + scope[\"type\"] + \".send\"\n+                        \" \".join((span_name, scope[\"type\"], \"send\"))\n                     ) as send_span:\n                         if send_span.is_recording():\n                             if message[\"type\"] == \"http.response.start\":\n", "issue": "Fix asgi instrumentation default span name\nSimilar to [wsgi](https://github.com/open-telemetry/opentelemetry-python/blob/master/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py#L171), the default span name for SERVER spans should be `HTTP <method>` as outlined by the [specs](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#name).\r\n\r\nPart of https://github.com/open-telemetry/opentelemetry-python-contrib/issues/434\n", "before_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"\nThe opentelemetry-instrumentation-asgi package provides an ASGI middleware that can be used\non any ASGI framework (such as Django-channels / Quart) to track requests\ntiming through OpenTelemetry.\n\"\"\"\n\nimport typing\nimport urllib\nfrom functools import wraps\nfrom typing import Tuple\n\nfrom asgiref.compatibility import guarantee_single_callable\n\nfrom opentelemetry import context, trace\nfrom opentelemetry.instrumentation.asgi.version import __version__  # noqa\nfrom opentelemetry.instrumentation.utils import http_status_to_status_code\nfrom opentelemetry.propagate import extract\nfrom opentelemetry.propagators.textmap import Getter\nfrom opentelemetry.semconv.trace import SpanAttributes\nfrom opentelemetry.trace.status import Status, StatusCode\n\n\nclass ASGIGetter(Getter):\n    def get(\n        self, carrier: dict, key: str\n    ) -> typing.Optional[typing.List[str]]:\n        \"\"\"Getter implementation to retrieve a HTTP header value from the ASGI\n        scope.\n\n        Args:\n            carrier: ASGI scope object\n            key: header name in scope\n        Returns:\n            A list with a single string with the header value if it exists,\n                else None.\n        \"\"\"\n        headers = carrier.get(\"headers\")\n        if not headers:\n            return None\n\n        # asgi header keys are in lower case\n        key = key.lower()\n        decoded = [\n            _value.decode(\"utf8\")\n            for (_key, _value) in headers\n            if _key.decode(\"utf8\") == key\n        ]\n        if not decoded:\n            return None\n        return decoded\n\n    def keys(self, carrier: dict) -> typing.List[str]:\n        return list(carrier.keys())\n\n\nasgi_getter = ASGIGetter()\n\n\ndef collect_request_attributes(scope):\n    \"\"\"Collects HTTP request attributes from the ASGI scope and returns a\n    dictionary to be used as span creation attributes.\"\"\"\n    server_host, port, http_url = get_host_port_url_tuple(scope)\n    query_string = scope.get(\"query_string\")\n    if query_string and http_url:\n        if isinstance(query_string, bytes):\n            query_string = query_string.decode(\"utf8\")\n        http_url = http_url + (\"?\" + urllib.parse.unquote(query_string))\n\n    result = {\n        SpanAttributes.HTTP_SCHEME: scope.get(\"scheme\"),\n        SpanAttributes.HTTP_HOST: server_host,\n        SpanAttributes.NET_HOST_PORT: port,\n        SpanAttributes.HTTP_FLAVOR: scope.get(\"http_version\"),\n        SpanAttributes.HTTP_TARGET: scope.get(\"path\"),\n        SpanAttributes.HTTP_URL: http_url,\n    }\n    http_method = scope.get(\"method\")\n    if http_method:\n        result[SpanAttributes.HTTP_METHOD] = http_method\n\n    http_host_value_list = asgi_getter.get(scope, \"host\")\n    if http_host_value_list:\n        result[SpanAttributes.HTTP_SERVER_NAME] = \",\".join(\n            http_host_value_list\n        )\n    http_user_agent = asgi_getter.get(scope, \"user-agent\")\n    if http_user_agent:\n        result[SpanAttributes.HTTP_USER_AGENT] = http_user_agent[0]\n\n    if \"client\" in scope and scope[\"client\"] is not None:\n        result[SpanAttributes.NET_PEER_IP] = scope.get(\"client\")[0]\n        result[SpanAttributes.NET_PEER_PORT] = scope.get(\"client\")[1]\n\n    # remove None values\n    result = {k: v for k, v in result.items() if v is not None}\n\n    return result\n\n\ndef get_host_port_url_tuple(scope):\n    \"\"\"Returns (host, port, full_url) tuple.\"\"\"\n    server = scope.get(\"server\") or [\"0.0.0.0\", 80]\n    port = server[1]\n    server_host = server[0] + (\":\" + str(port) if port != 80 else \"\")\n    full_path = scope.get(\"root_path\", \"\") + scope.get(\"path\", \"\")\n    http_url = scope.get(\"scheme\", \"http\") + \"://\" + server_host + full_path\n    return server_host, port, http_url\n\n\ndef set_status_code(span, status_code):\n    \"\"\"Adds HTTP response attributes to span using the status_code argument.\"\"\"\n    if not span.is_recording():\n        return\n    try:\n        status_code = int(status_code)\n    except ValueError:\n        span.set_status(\n            Status(\n                StatusCode.ERROR,\n                \"Non-integer HTTP status: \" + repr(status_code),\n            )\n        )\n    else:\n        span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)\n        span.set_status(Status(http_status_to_status_code(status_code)))\n\n\ndef get_default_span_details(scope: dict) -> Tuple[str, dict]:\n    \"\"\"Default implementation for span_details_callback\n\n    Args:\n        scope: the asgi scope dictionary\n\n    Returns:\n        a tuple of the span, and any attributes to attach to the\n        span.\n    \"\"\"\n    method_or_path = scope.get(\"method\") or scope.get(\"path\")\n\n    return method_or_path, {}\n\n\nclass OpenTelemetryMiddleware:\n    \"\"\"The ASGI application middleware.\n\n    This class is an ASGI middleware that starts and annotates spans for any\n    requests it is invoked with.\n\n    Args:\n        app: The ASGI application callable to forward requests to.\n        span_details_callback: Callback which should return a string\n            and a tuple, representing the desired span name and a\n            dictionary with any additional span attributes to set.\n            Optional: Defaults to get_default_span_details.\n        tracer_provider: The optional tracer provider to use. If omitted\n            the current globally configured one is used.\n    \"\"\"\n\n    def __init__(\n        self,\n        app,\n        excluded_urls=None,\n        span_details_callback=None,\n        tracer_provider=None,\n    ):\n        self.app = guarantee_single_callable(app)\n        self.tracer = trace.get_tracer(__name__, __version__, tracer_provider)\n        self.span_details_callback = (\n            span_details_callback or get_default_span_details\n        )\n        self.excluded_urls = excluded_urls\n\n    async def __call__(self, scope, receive, send):\n        \"\"\"The ASGI application\n\n        Args:\n            scope: A ASGI environment.\n            receive: An awaitable callable yielding dictionaries\n            send: An awaitable callable taking a single dictionary as argument.\n        \"\"\"\n        if scope[\"type\"] not in (\"http\", \"websocket\"):\n            return await self.app(scope, receive, send)\n\n        _, _, url = get_host_port_url_tuple(scope)\n        if self.excluded_urls and self.excluded_urls.url_disabled(url):\n            return await self.app(scope, receive, send)\n\n        token = context.attach(extract(scope, getter=asgi_getter))\n        span_name, additional_attributes = self.span_details_callback(scope)\n\n        try:\n            with self.tracer.start_as_current_span(\n                span_name + \" asgi\", kind=trace.SpanKind.SERVER,\n            ) as span:\n                if span.is_recording():\n                    attributes = collect_request_attributes(scope)\n                    attributes.update(additional_attributes)\n                    for key, value in attributes.items():\n                        span.set_attribute(key, value)\n\n                @wraps(receive)\n                async def wrapped_receive():\n                    with self.tracer.start_as_current_span(\n                        span_name + \" asgi.\" + scope[\"type\"] + \".receive\"\n                    ) as receive_span:\n                        message = await receive()\n                        if receive_span.is_recording():\n                            if message[\"type\"] == \"websocket.receive\":\n                                set_status_code(receive_span, 200)\n                            receive_span.set_attribute(\"type\", message[\"type\"])\n                    return message\n\n                @wraps(send)\n                async def wrapped_send(message):\n                    with self.tracer.start_as_current_span(\n                        span_name + \" asgi.\" + scope[\"type\"] + \".send\"\n                    ) as send_span:\n                        if send_span.is_recording():\n                            if message[\"type\"] == \"http.response.start\":\n                                status_code = message[\"status\"]\n                                set_status_code(span, status_code)\n                                set_status_code(send_span, status_code)\n                            elif message[\"type\"] == \"websocket.send\":\n                                set_status_code(span, 200)\n                                set_status_code(send_span, 200)\n                            send_span.set_attribute(\"type\", message[\"type\"])\n                        await send(message)\n\n                await self.app(scope, wrapped_receive, wrapped_send)\n        finally:\n            context.detach(token)\n", "path": "instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py"}], "after_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"\nThe opentelemetry-instrumentation-asgi package provides an ASGI middleware that can be used\non any ASGI framework (such as Django-channels / Quart) to track requests\ntiming through OpenTelemetry.\n\"\"\"\n\nimport typing\nimport urllib\nfrom functools import wraps\nfrom typing import Tuple\n\nfrom asgiref.compatibility import guarantee_single_callable\n\nfrom opentelemetry import context, trace\nfrom opentelemetry.instrumentation.asgi.version import __version__  # noqa\nfrom opentelemetry.instrumentation.utils import http_status_to_status_code\nfrom opentelemetry.propagate import extract\nfrom opentelemetry.propagators.textmap import Getter\nfrom opentelemetry.semconv.trace import SpanAttributes\nfrom opentelemetry.trace.status import Status, StatusCode\n\n\nclass ASGIGetter(Getter):\n    def get(\n        self, carrier: dict, key: str\n    ) -> typing.Optional[typing.List[str]]:\n        \"\"\"Getter implementation to retrieve a HTTP header value from the ASGI\n        scope.\n\n        Args:\n            carrier: ASGI scope object\n            key: header name in scope\n        Returns:\n            A list with a single string with the header value if it exists,\n                else None.\n        \"\"\"\n        headers = carrier.get(\"headers\")\n        if not headers:\n            return None\n\n        # asgi header keys are in lower case\n        key = key.lower()\n        decoded = [\n            _value.decode(\"utf8\")\n            for (_key, _value) in headers\n            if _key.decode(\"utf8\") == key\n        ]\n        if not decoded:\n            return None\n        return decoded\n\n    def keys(self, carrier: dict) -> typing.List[str]:\n        return list(carrier.keys())\n\n\nasgi_getter = ASGIGetter()\n\n\ndef collect_request_attributes(scope):\n    \"\"\"Collects HTTP request attributes from the ASGI scope and returns a\n    dictionary to be used as span creation attributes.\"\"\"\n    server_host, port, http_url = get_host_port_url_tuple(scope)\n    query_string = scope.get(\"query_string\")\n    if query_string and http_url:\n        if isinstance(query_string, bytes):\n            query_string = query_string.decode(\"utf8\")\n        http_url = http_url + (\"?\" + urllib.parse.unquote(query_string))\n\n    result = {\n        SpanAttributes.HTTP_SCHEME: scope.get(\"scheme\"),\n        SpanAttributes.HTTP_HOST: server_host,\n        SpanAttributes.NET_HOST_PORT: port,\n        SpanAttributes.HTTP_FLAVOR: scope.get(\"http_version\"),\n        SpanAttributes.HTTP_TARGET: scope.get(\"path\"),\n        SpanAttributes.HTTP_URL: http_url,\n    }\n    http_method = scope.get(\"method\")\n    if http_method:\n        result[SpanAttributes.HTTP_METHOD] = http_method\n\n    http_host_value_list = asgi_getter.get(scope, \"host\")\n    if http_host_value_list:\n        result[SpanAttributes.HTTP_SERVER_NAME] = \",\".join(\n            http_host_value_list\n        )\n    http_user_agent = asgi_getter.get(scope, \"user-agent\")\n    if http_user_agent:\n        result[SpanAttributes.HTTP_USER_AGENT] = http_user_agent[0]\n\n    if \"client\" in scope and scope[\"client\"] is not None:\n        result[SpanAttributes.NET_PEER_IP] = scope.get(\"client\")[0]\n        result[SpanAttributes.NET_PEER_PORT] = scope.get(\"client\")[1]\n\n    # remove None values\n    result = {k: v for k, v in result.items() if v is not None}\n\n    return result\n\n\ndef get_host_port_url_tuple(scope):\n    \"\"\"Returns (host, port, full_url) tuple.\"\"\"\n    server = scope.get(\"server\") or [\"0.0.0.0\", 80]\n    port = server[1]\n    server_host = server[0] + (\":\" + str(port) if port != 80 else \"\")\n    full_path = scope.get(\"root_path\", \"\") + scope.get(\"path\", \"\")\n    http_url = scope.get(\"scheme\", \"http\") + \"://\" + server_host + full_path\n    return server_host, port, http_url\n\n\ndef set_status_code(span, status_code):\n    \"\"\"Adds HTTP response attributes to span using the status_code argument.\"\"\"\n    if not span.is_recording():\n        return\n    try:\n        status_code = int(status_code)\n    except ValueError:\n        span.set_status(\n            Status(\n                StatusCode.ERROR,\n                \"Non-integer HTTP status: \" + repr(status_code),\n            )\n        )\n    else:\n        span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)\n        span.set_status(Status(http_status_to_status_code(status_code)))\n\n\ndef get_default_span_details(scope: dict) -> Tuple[str, dict]:\n    \"\"\"Default implementation for span_details_callback\n\n    Args:\n        scope: the asgi scope dictionary\n\n    Returns:\n        a tuple of the span name, and any attributes to attach to the span.\n    \"\"\"\n    span_name = scope.get(\"path\", \"\").strip() or \"HTTP {}\".format(\n        scope.get(\"method\", \"\").strip()\n    )\n\n    return span_name, {}\n\n\nclass OpenTelemetryMiddleware:\n    \"\"\"The ASGI application middleware.\n\n    This class is an ASGI middleware that starts and annotates spans for any\n    requests it is invoked with.\n\n    Args:\n        app: The ASGI application callable to forward requests to.\n        span_details_callback: Callback which should return a string\n            and a tuple, representing the desired span name and a\n            dictionary with any additional span attributes to set.\n            Optional: Defaults to get_default_span_details.\n        tracer_provider: The optional tracer provider to use. If omitted\n            the current globally configured one is used.\n    \"\"\"\n\n    def __init__(\n        self,\n        app,\n        excluded_urls=None,\n        span_details_callback=None,\n        tracer_provider=None,\n    ):\n        self.app = guarantee_single_callable(app)\n        self.tracer = trace.get_tracer(__name__, __version__, tracer_provider)\n        self.span_details_callback = (\n            span_details_callback or get_default_span_details\n        )\n        self.excluded_urls = excluded_urls\n\n    async def __call__(self, scope, receive, send):\n        \"\"\"The ASGI application\n\n        Args:\n            scope: A ASGI environment.\n            receive: An awaitable callable yielding dictionaries\n            send: An awaitable callable taking a single dictionary as argument.\n        \"\"\"\n        if scope[\"type\"] not in (\"http\", \"websocket\"):\n            return await self.app(scope, receive, send)\n\n        _, _, url = get_host_port_url_tuple(scope)\n        if self.excluded_urls and self.excluded_urls.url_disabled(url):\n            return await self.app(scope, receive, send)\n\n        token = context.attach(extract(scope, getter=asgi_getter))\n        span_name, additional_attributes = self.span_details_callback(scope)\n\n        try:\n            with self.tracer.start_as_current_span(\n                span_name, kind=trace.SpanKind.SERVER,\n            ) as span:\n                if span.is_recording():\n                    attributes = collect_request_attributes(scope)\n                    attributes.update(additional_attributes)\n                    for key, value in attributes.items():\n                        span.set_attribute(key, value)\n\n                @wraps(receive)\n                async def wrapped_receive():\n                    with self.tracer.start_as_current_span(\n                        \" \".join((span_name, scope[\"type\"], \"receive\"))\n                    ) as receive_span:\n                        message = await receive()\n                        if receive_span.is_recording():\n                            if message[\"type\"] == \"websocket.receive\":\n                                set_status_code(receive_span, 200)\n                            receive_span.set_attribute(\"type\", message[\"type\"])\n                    return message\n\n                @wraps(send)\n                async def wrapped_send(message):\n                    with self.tracer.start_as_current_span(\n                        \" \".join((span_name, scope[\"type\"], \"send\"))\n                    ) as send_span:\n                        if send_span.is_recording():\n                            if message[\"type\"] == \"http.response.start\":\n                                status_code = message[\"status\"]\n                                set_status_code(span, status_code)\n                                set_status_code(send_span, status_code)\n                            elif message[\"type\"] == \"websocket.send\":\n                                set_status_code(span, 200)\n                                set_status_code(send_span, 200)\n                            send_span.set_attribute(\"type\", message[\"type\"])\n                        await send(message)\n\n                await self.app(scope, wrapped_receive, wrapped_send)\n        finally:\n            context.detach(token)\n", "path": "instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py"}]}