We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Starlette/Fastapi: endpoint duration includes the duration of background tasks
### Which version of dd-trace-py are you using?

ddtrace==0.55.4

### Which version of pip are you using?

21.2.4


### Which version of the libraries are you using?

fastapi==0.68.2
starlette==0.14.2

### How can we reproduce your problem?

this would be a minimal proof of concept `app.py`, running through `ddtrace-run uvicorn app:app`

```
import asyncio

from ddtrace import tracer
from fastapi import FastAPI, BackgroundTasks

app = FastAPI()


async def some_background_task():
    with tracer.start_span("some_background_task", activate=True):
        tracer.context_provider.activate(None)
        await asyncio.sleep(10)


@app.get("/")
async def main(background_tasks: BackgroundTasks):
    background_tasks.add_task(some_background_task)
    return "ok"

```

### What is the result that you get?

The duration of `/` is reported to be 10s, while the browser immediately receives the response.
`some_background_task` is also reported with a duration of 10s.

### What is the result that you expected?

I would expect that the reported endpoint duration matches the time it took to get the response, and that the background task is reported separately. Please don't mind that `tracer.context_provider.activate(None)` might be redundant here, adding it here to show what I have tried.

FastAPI's `add_task` actually comes from starlette https://www.starlette.io/background/

I can understand why the endpoint duration includes the background task, this is the definition of starlette's `Response.__call__`:

https://github.com/encode/starlette/blob/ada99beee530e7b841ce320bc6e66f6dbd9ad781/starlette/responses.py#L159
```
    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        await send(
            {
                "type": "http.response.start",
                "status": self.status_code,
                "headers": self.raw_headers,
            }
        )
        await send({"type": "http.response.body", "body": self.body})

        if self.background is not None:
            await self.background()
```

The response header and body is sent, but the function itself is not finished until all background tasks have been processed.

I believe that this is not what users of ddtrace would expect: the background tasks are used to return a response early without waiting for background operations to finish ; the reported endpoint duration should correspond to when the body was sent


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ddtrace/contrib/asgi/middleware.py`
Content:
```
1 import sys
2 from typing import TYPE_CHECKING
3 
4 import ddtrace
5 from ddtrace import config
6 from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY
7 from ddtrace.ext import SpanTypes
8 from ddtrace.ext import http
9 
10 from .. import trace_utils
11 from ...internal.compat import reraise
12 from ...internal.logger import get_logger
13 from .utils import guarantee_single_callable
14 
15 
16 if TYPE_CHECKING:
17     from typing import Any
18     from typing import Mapping
19     from typing import Optional
20 
21     from ddtrace import Span
22 
23 
24 log = get_logger(__name__)
25 
26 config._add(
27     "asgi",
28     dict(service_name=config._get_service(default="asgi"), request_span_name="asgi.request", distributed_tracing=True),
29 )
30 
31 ASGI_VERSION = "asgi.version"
32 ASGI_SPEC_VERSION = "asgi.spec_version"
33 
34 
35 def bytes_to_str(str_or_bytes):
36     return str_or_bytes.decode() if isinstance(str_or_bytes, bytes) else str_or_bytes
37 
38 
39 def _extract_versions_from_scope(scope, integration_config):
40     tags = {}
41 
42     http_version = scope.get("http_version")
43     if http_version:
44         tags[http.VERSION] = http_version
45 
46     scope_asgi = scope.get("asgi")
47 
48     if scope_asgi and "version" in scope_asgi:
49         tags[ASGI_VERSION] = scope_asgi["version"]
50 
51     if scope_asgi and "spec_version" in scope_asgi:
52         tags[ASGI_SPEC_VERSION] = scope_asgi["spec_version"]
53 
54     return tags
55 
56 
57 def _extract_headers(scope):
58     headers = scope.get("headers")
59     if headers:
60         # headers: (Iterable[[byte string, byte string]])
61         return dict((bytes_to_str(k), bytes_to_str(v)) for (k, v) in headers)
62     return {}
63 
64 
65 def _default_handle_exception_span(exc, span):
66     """Default handler for exception for span"""
67     span.set_tag(http.STATUS_CODE, 500)
68 
69 
70 def span_from_scope(scope):
71     # type: (Mapping[str, Any]) -> Optional[Span]
72     """Retrieve the top-level ASGI span from the scope."""
73     return scope.get("datadog", {}).get("request_spans", [None])[0]
74 
75 
76 class TraceMiddleware:
77     """
78     ASGI application middleware that traces the requests.
79     Args:
80         app: The ASGI application.
81         tracer: Custom tracer. Defaults to the global tracer.
82     """
83 
84     def __init__(
85         self,
86         app,
87         tracer=None,
88         integration_config=config.asgi,
89         handle_exception_span=_default_handle_exception_span,
90         span_modifier=None,
91     ):
92         self.app = guarantee_single_callable(app)
93         self.tracer = tracer or ddtrace.tracer
94         self.integration_config = integration_config
95         self.handle_exception_span = handle_exception_span
96         self.span_modifier = span_modifier
97 
98     async def __call__(self, scope, receive, send):
99         if scope["type"] != "http":
100             return await self.app(scope, receive, send)
101 
102         try:
103             headers = _extract_headers(scope)
104         except Exception:
105             log.warning("failed to decode headers for distributed tracing", exc_info=True)
106             headers = {}
107         else:
108             trace_utils.activate_distributed_headers(
109                 self.tracer, int_config=self.integration_config, request_headers=headers
110             )
111 
112         resource = "{} {}".format(scope["method"], scope["path"])
113 
114         span = self.tracer.trace(
115             name=self.integration_config.get("request_span_name", "asgi.request"),
116             service=trace_utils.int_service(None, self.integration_config),
117             resource=resource,
118             span_type=SpanTypes.WEB,
119         )
120 
121         if "datadog" not in scope:
122             scope["datadog"] = {"request_spans": [span]}
123         else:
124             scope["datadog"]["request_spans"].append(span)
125 
126         if self.span_modifier:
127             self.span_modifier(span, scope)
128 
129         sample_rate = self.integration_config.get_analytics_sample_rate(use_global_config=True)
130         if sample_rate is not None:
131             span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate)
132 
133         method = scope.get("method")
134         server = scope.get("server")
135         if server and len(server) == 2:
136             port = server[1]
137             server_host = server[0] + (":" + str(port) if port is not None and port != 80 else "")
138             full_path = scope.get("root_path", "") + scope.get("path", "")
139             url = scope.get("scheme", "http") + "://" + server_host + full_path
140         else:
141             url = None
142 
143         if self.integration_config.trace_query_string:
144             query_string = scope.get("query_string")
145             if len(query_string) > 0:
146                 query_string = bytes_to_str(query_string)
147         else:
148             query_string = None
149 
150         trace_utils.set_http_meta(
151             span, self.integration_config, method=method, url=url, query=query_string, request_headers=headers
152         )
153 
154         tags = _extract_versions_from_scope(scope, self.integration_config)
155         span.set_tags(tags)
156 
157         async def wrapped_send(message):
158             if span and message.get("type") == "http.response.start" and "status" in message:
159                 status_code = message["status"]
160             else:
161                 status_code = None
162 
163             if "headers" in message:
164                 response_headers = message["headers"]
165             else:
166                 response_headers = None
167 
168             trace_utils.set_http_meta(
169                 span, self.integration_config, status_code=status_code, response_headers=response_headers
170             )
171 
172             return await send(message)
173 
174         try:
175             return await self.app(scope, receive, wrapped_send)
176         except Exception as exc:
177             (exc_type, exc_val, exc_tb) = sys.exc_info()
178             span.set_exc_info(exc_type, exc_val, exc_tb)
179             self.handle_exception_span(exc, span)
180             reraise(exc_type, exc_val, exc_tb)
181         finally:
182             try:
183                 del scope["datadog"]["request_span"]
184             except KeyError:
185                 pass
186             span.finish()
187
```
--- 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/ddtrace/contrib/asgi/middleware.py b/ddtrace/contrib/asgi/middleware.py
--- a/ddtrace/contrib/asgi/middleware.py
+++ b/ddtrace/contrib/asgi/middleware.py
@@ -169,7 +169,13 @@
                 span, self.integration_config, status_code=status_code, response_headers=response_headers
             )
 
-            return await send(message)
+            try:
+                return await send(message)
+            finally:
+                # Per asgi spec, "more_body" is used if there is still data to send
+                # Close the span if "http.response.body" has no more data left to send in the response.
+                if message.get("type") == "http.response.body" and not message.get("more_body", False):
+                    span.finish()
 
         try:
             return await self.app(scope, receive, wrapped_send)
@@ -183,4 +189,5 @@
                 del scope["datadog"]["request_span"]
             except KeyError:
                 pass
+
             span.finish()

{"golden_diff": "diff --git a/ddtrace/contrib/asgi/middleware.py b/ddtrace/contrib/asgi/middleware.py\n--- a/ddtrace/contrib/asgi/middleware.py\n+++ b/ddtrace/contrib/asgi/middleware.py\n@@ -169,7 +169,13 @@\n                 span, self.integration_config, status_code=status_code, response_headers=response_headers\n             )\n \n-            return await send(message)\n+            try:\n+                return await send(message)\n+            finally:\n+                # Per asgi spec, \"more_body\" is used if there is still data to send\n+                # Close the span if \"http.response.body\" has no more data left to send in the response.\n+                if message.get(\"type\") == \"http.response.body\" and not message.get(\"more_body\", False):\n+                    span.finish()\n \n         try:\n             return await self.app(scope, receive, wrapped_send)\n@@ -183,4 +189,5 @@\n                 del scope[\"datadog\"][\"request_span\"]\n             except KeyError:\n                 pass\n+\n             span.finish()\n", "issue": "Starlette/Fastapi: endpoint duration includes the duration of background tasks\n### Which version of dd-trace-py are you using?\r\n\r\nddtrace==0.55.4\r\n\r\n### Which version of pip are you using?\r\n\r\n21.2.4\r\n\r\n\r\n### Which version of the libraries are you using?\r\n\r\nfastapi==0.68.2\r\nstarlette==0.14.2\r\n\r\n### How can we reproduce your problem?\r\n\r\nthis would be a minimal proof of concept `app.py`, running through `ddtrace-run uvicorn app:app`\r\n\r\n```\r\nimport asyncio\r\n\r\nfrom ddtrace import tracer\r\nfrom fastapi import FastAPI, BackgroundTasks\r\n\r\napp = FastAPI()\r\n\r\n\r\nasync def some_background_task():\r\n    with tracer.start_span(\"some_background_task\", activate=True):\r\n        tracer.context_provider.activate(None)\r\n        await asyncio.sleep(10)\r\n\r\n\r\n@app.get(\"/\")\r\nasync def main(background_tasks: BackgroundTasks):\r\n    background_tasks.add_task(some_background_task)\r\n    return \"ok\"\r\n\r\n```\r\n\r\n### What is the result that you get?\r\n\r\nThe duration of `/` is reported to be 10s, while the browser immediately receives the response.\r\n`some_background_task` is also reported with a duration of 10s.\r\n\r\n### What is the result that you expected?\r\n\r\nI would expect that the reported endpoint duration matches the time it took to get the response, and that the background task is reported separately. Please don't mind that `tracer.context_provider.activate(None)` might be redundant here, adding it here to show what I have tried.\r\n\r\nFastAPI's `add_task` actually comes from starlette https://www.starlette.io/background/\r\n\r\nI can understand why the endpoint duration includes the background task, this is the definition of starlette's `Response.__call__`:\r\n\r\nhttps://github.com/encode/starlette/blob/ada99beee530e7b841ce320bc6e66f6dbd9ad781/starlette/responses.py#L159\r\n```\r\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\r\n        await send(\r\n            {\r\n                \"type\": \"http.response.start\",\r\n                \"status\": self.status_code,\r\n                \"headers\": self.raw_headers,\r\n            }\r\n        )\r\n        await send({\"type\": \"http.response.body\", \"body\": self.body})\r\n\r\n        if self.background is not None:\r\n            await self.background()\r\n```\r\n\r\nThe response header and body is sent, but the function itself is not finished until all background tasks have been processed.\r\n\r\nI believe that this is not what users of ddtrace would expect: the background tasks are used to return a response early without waiting for background operations to finish ; the reported endpoint duration should correspond to when the body was sent\r\n\n", "before_files": [{"content": "import sys\nfrom typing import TYPE_CHECKING\n\nimport ddtrace\nfrom ddtrace import config\nfrom ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY\nfrom ddtrace.ext import SpanTypes\nfrom ddtrace.ext import http\n\nfrom .. import trace_utils\nfrom ...internal.compat import reraise\nfrom ...internal.logger import get_logger\nfrom .utils import guarantee_single_callable\n\n\nif TYPE_CHECKING:\n    from typing import Any\n    from typing import Mapping\n    from typing import Optional\n\n    from ddtrace import Span\n\n\nlog = get_logger(__name__)\n\nconfig._add(\n    \"asgi\",\n    dict(service_name=config._get_service(default=\"asgi\"), request_span_name=\"asgi.request\", distributed_tracing=True),\n)\n\nASGI_VERSION = \"asgi.version\"\nASGI_SPEC_VERSION = \"asgi.spec_version\"\n\n\ndef bytes_to_str(str_or_bytes):\n    return str_or_bytes.decode() if isinstance(str_or_bytes, bytes) else str_or_bytes\n\n\ndef _extract_versions_from_scope(scope, integration_config):\n    tags = {}\n\n    http_version = scope.get(\"http_version\")\n    if http_version:\n        tags[http.VERSION] = http_version\n\n    scope_asgi = scope.get(\"asgi\")\n\n    if scope_asgi and \"version\" in scope_asgi:\n        tags[ASGI_VERSION] = scope_asgi[\"version\"]\n\n    if scope_asgi and \"spec_version\" in scope_asgi:\n        tags[ASGI_SPEC_VERSION] = scope_asgi[\"spec_version\"]\n\n    return tags\n\n\ndef _extract_headers(scope):\n    headers = scope.get(\"headers\")\n    if headers:\n        # headers: (Iterable[[byte string, byte string]])\n        return dict((bytes_to_str(k), bytes_to_str(v)) for (k, v) in headers)\n    return {}\n\n\ndef _default_handle_exception_span(exc, span):\n    \"\"\"Default handler for exception for span\"\"\"\n    span.set_tag(http.STATUS_CODE, 500)\n\n\ndef span_from_scope(scope):\n    # type: (Mapping[str, Any]) -> Optional[Span]\n    \"\"\"Retrieve the top-level ASGI span from the scope.\"\"\"\n    return scope.get(\"datadog\", {}).get(\"request_spans\", [None])[0]\n\n\nclass TraceMiddleware:\n    \"\"\"\n    ASGI application middleware that traces the requests.\n    Args:\n        app: The ASGI application.\n        tracer: Custom tracer. Defaults to the global tracer.\n    \"\"\"\n\n    def __init__(\n        self,\n        app,\n        tracer=None,\n        integration_config=config.asgi,\n        handle_exception_span=_default_handle_exception_span,\n        span_modifier=None,\n    ):\n        self.app = guarantee_single_callable(app)\n        self.tracer = tracer or ddtrace.tracer\n        self.integration_config = integration_config\n        self.handle_exception_span = handle_exception_span\n        self.span_modifier = span_modifier\n\n    async def __call__(self, scope, receive, send):\n        if scope[\"type\"] != \"http\":\n            return await self.app(scope, receive, send)\n\n        try:\n            headers = _extract_headers(scope)\n        except Exception:\n            log.warning(\"failed to decode headers for distributed tracing\", exc_info=True)\n            headers = {}\n        else:\n            trace_utils.activate_distributed_headers(\n                self.tracer, int_config=self.integration_config, request_headers=headers\n            )\n\n        resource = \"{} {}\".format(scope[\"method\"], scope[\"path\"])\n\n        span = self.tracer.trace(\n            name=self.integration_config.get(\"request_span_name\", \"asgi.request\"),\n            service=trace_utils.int_service(None, self.integration_config),\n            resource=resource,\n            span_type=SpanTypes.WEB,\n        )\n\n        if \"datadog\" not in scope:\n            scope[\"datadog\"] = {\"request_spans\": [span]}\n        else:\n            scope[\"datadog\"][\"request_spans\"].append(span)\n\n        if self.span_modifier:\n            self.span_modifier(span, scope)\n\n        sample_rate = self.integration_config.get_analytics_sample_rate(use_global_config=True)\n        if sample_rate is not None:\n            span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate)\n\n        method = scope.get(\"method\")\n        server = scope.get(\"server\")\n        if server and len(server) == 2:\n            port = server[1]\n            server_host = server[0] + (\":\" + str(port) if port is not None and port != 80 else \"\")\n            full_path = scope.get(\"root_path\", \"\") + scope.get(\"path\", \"\")\n            url = scope.get(\"scheme\", \"http\") + \"://\" + server_host + full_path\n        else:\n            url = None\n\n        if self.integration_config.trace_query_string:\n            query_string = scope.get(\"query_string\")\n            if len(query_string) > 0:\n                query_string = bytes_to_str(query_string)\n        else:\n            query_string = None\n\n        trace_utils.set_http_meta(\n            span, self.integration_config, method=method, url=url, query=query_string, request_headers=headers\n        )\n\n        tags = _extract_versions_from_scope(scope, self.integration_config)\n        span.set_tags(tags)\n\n        async def wrapped_send(message):\n            if span and message.get(\"type\") == \"http.response.start\" and \"status\" in message:\n                status_code = message[\"status\"]\n            else:\n                status_code = None\n\n            if \"headers\" in message:\n                response_headers = message[\"headers\"]\n            else:\n                response_headers = None\n\n            trace_utils.set_http_meta(\n                span, self.integration_config, status_code=status_code, response_headers=response_headers\n            )\n\n            return await send(message)\n\n        try:\n            return await self.app(scope, receive, wrapped_send)\n        except Exception as exc:\n            (exc_type, exc_val, exc_tb) = sys.exc_info()\n            span.set_exc_info(exc_type, exc_val, exc_tb)\n            self.handle_exception_span(exc, span)\n            reraise(exc_type, exc_val, exc_tb)\n        finally:\n            try:\n                del scope[\"datadog\"][\"request_span\"]\n            except KeyError:\n                pass\n            span.finish()\n", "path": "ddtrace/contrib/asgi/middleware.py"}], "after_files": [{"content": "import sys\nfrom typing import TYPE_CHECKING\n\nimport ddtrace\nfrom ddtrace import config\nfrom ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY\nfrom ddtrace.ext import SpanTypes\nfrom ddtrace.ext import http\n\nfrom .. import trace_utils\nfrom ...internal.compat import reraise\nfrom ...internal.logger import get_logger\nfrom .utils import guarantee_single_callable\n\n\nif TYPE_CHECKING:\n    from typing import Any\n    from typing import Mapping\n    from typing import Optional\n\n    from ddtrace import Span\n\n\nlog = get_logger(__name__)\n\nconfig._add(\n    \"asgi\",\n    dict(service_name=config._get_service(default=\"asgi\"), request_span_name=\"asgi.request\", distributed_tracing=True),\n)\n\nASGI_VERSION = \"asgi.version\"\nASGI_SPEC_VERSION = \"asgi.spec_version\"\n\n\ndef bytes_to_str(str_or_bytes):\n    return str_or_bytes.decode() if isinstance(str_or_bytes, bytes) else str_or_bytes\n\n\ndef _extract_versions_from_scope(scope, integration_config):\n    tags = {}\n\n    http_version = scope.get(\"http_version\")\n    if http_version:\n        tags[http.VERSION] = http_version\n\n    scope_asgi = scope.get(\"asgi\")\n\n    if scope_asgi and \"version\" in scope_asgi:\n        tags[ASGI_VERSION] = scope_asgi[\"version\"]\n\n    if scope_asgi and \"spec_version\" in scope_asgi:\n        tags[ASGI_SPEC_VERSION] = scope_asgi[\"spec_version\"]\n\n    return tags\n\n\ndef _extract_headers(scope):\n    headers = scope.get(\"headers\")\n    if headers:\n        # headers: (Iterable[[byte string, byte string]])\n        return dict((bytes_to_str(k), bytes_to_str(v)) for (k, v) in headers)\n    return {}\n\n\ndef _default_handle_exception_span(exc, span):\n    \"\"\"Default handler for exception for span\"\"\"\n    span.set_tag(http.STATUS_CODE, 500)\n\n\ndef span_from_scope(scope):\n    # type: (Mapping[str, Any]) -> Optional[Span]\n    \"\"\"Retrieve the top-level ASGI span from the scope.\"\"\"\n    return scope.get(\"datadog\", {}).get(\"request_spans\", [None])[0]\n\n\nclass TraceMiddleware:\n    \"\"\"\n    ASGI application middleware that traces the requests.\n    Args:\n        app: The ASGI application.\n        tracer: Custom tracer. Defaults to the global tracer.\n    \"\"\"\n\n    def __init__(\n        self,\n        app,\n        tracer=None,\n        integration_config=config.asgi,\n        handle_exception_span=_default_handle_exception_span,\n        span_modifier=None,\n    ):\n        self.app = guarantee_single_callable(app)\n        self.tracer = tracer or ddtrace.tracer\n        self.integration_config = integration_config\n        self.handle_exception_span = handle_exception_span\n        self.span_modifier = span_modifier\n\n    async def __call__(self, scope, receive, send):\n        if scope[\"type\"] != \"http\":\n            return await self.app(scope, receive, send)\n\n        try:\n            headers = _extract_headers(scope)\n        except Exception:\n            log.warning(\"failed to decode headers for distributed tracing\", exc_info=True)\n            headers = {}\n        else:\n            trace_utils.activate_distributed_headers(\n                self.tracer, int_config=self.integration_config, request_headers=headers\n            )\n\n        resource = \"{} {}\".format(scope[\"method\"], scope[\"path\"])\n\n        span = self.tracer.trace(\n            name=self.integration_config.get(\"request_span_name\", \"asgi.request\"),\n            service=trace_utils.int_service(None, self.integration_config),\n            resource=resource,\n            span_type=SpanTypes.WEB,\n        )\n\n        if \"datadog\" not in scope:\n            scope[\"datadog\"] = {\"request_spans\": [span]}\n        else:\n            scope[\"datadog\"][\"request_spans\"].append(span)\n\n        if self.span_modifier:\n            self.span_modifier(span, scope)\n\n        sample_rate = self.integration_config.get_analytics_sample_rate(use_global_config=True)\n        if sample_rate is not None:\n            span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate)\n\n        method = scope.get(\"method\")\n        server = scope.get(\"server\")\n        if server and len(server) == 2:\n            port = server[1]\n            server_host = server[0] + (\":\" + str(port) if port is not None and port != 80 else \"\")\n            full_path = scope.get(\"root_path\", \"\") + scope.get(\"path\", \"\")\n            url = scope.get(\"scheme\", \"http\") + \"://\" + server_host + full_path\n        else:\n            url = None\n\n        if self.integration_config.trace_query_string:\n            query_string = scope.get(\"query_string\")\n            if len(query_string) > 0:\n                query_string = bytes_to_str(query_string)\n        else:\n            query_string = None\n\n        trace_utils.set_http_meta(\n            span, self.integration_config, method=method, url=url, query=query_string, request_headers=headers\n        )\n\n        tags = _extract_versions_from_scope(scope, self.integration_config)\n        span.set_tags(tags)\n\n        async def wrapped_send(message):\n            if span and message.get(\"type\") == \"http.response.start\" and \"status\" in message:\n                status_code = message[\"status\"]\n            else:\n                status_code = None\n\n            if \"headers\" in message:\n                response_headers = message[\"headers\"]\n            else:\n                response_headers = None\n\n            trace_utils.set_http_meta(\n                span, self.integration_config, status_code=status_code, response_headers=response_headers\n            )\n\n            try:\n                return await send(message)\n            finally:\n                # Per asgi spec, \"more_body\" is used if there is still data to send\n                # Close the span if \"http.response.body\" has no more data left to send in the response.\n                if message.get(\"type\") == \"http.response.body\" and not message.get(\"more_body\", False):\n                    span.finish()\n\n        try:\n            return await self.app(scope, receive, wrapped_send)\n        except Exception as exc:\n            (exc_type, exc_val, exc_tb) = sys.exc_info()\n            span.set_exc_info(exc_type, exc_val, exc_tb)\n            self.handle_exception_span(exc, span)\n            reraise(exc_type, exc_val, exc_tb)\n        finally:\n            try:\n                del scope[\"datadog\"][\"request_span\"]\n            except KeyError:\n                pass\n\n            span.finish()\n", "path": "ddtrace/contrib/asgi/middleware.py"}]}