Skip to content

Commit 1688491

Browse files
authored
fix(asgi): ensure error tags are set on request spans (backports #5818 to 1.9) (#5844)
1 parent 167ae88 commit 1688491

File tree

4 files changed

+51
-27
lines changed

4 files changed

+51
-27
lines changed

ddtrace/contrib/asgi/middleware.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -208,14 +208,19 @@ async def wrapped_send(message):
208208
trace_utils.set_http_meta(
209209
span, self.integration_config, status_code=status_code, response_headers=response_headers
210210
)
211-
212211
try:
213212
return await send(message)
214213
finally:
215214
# Per asgi spec, "more_body" is used if there is still data to send
216215
# Close the span if "http.response.body" has no more data left to send in the
217216
# response.
218-
if message.get("type") == "http.response.body" and not message.get("more_body", False):
217+
if (
218+
message.get("type") == "http.response.body"
219+
and not message.get("more_body", False)
220+
# If the span has an error status code delay finishing the span until the
221+
# traceback and exception message is available
222+
and span.error == 0
223+
):
219224
span.finish()
220225

221226
async def wrapped_blocked_send(message):
@@ -237,7 +242,7 @@ async def wrapped_blocked_send(message):
237242
try:
238243
return await send(message)
239244
finally:
240-
if message.get("type") == "http.response.body":
245+
if message.get("type") == "http.response.body" and span.error == 0:
241246
span.finish()
242247

243248
try:
@@ -250,9 +255,6 @@ async def wrapped_blocked_send(message):
250255
self.handle_exception_span(exc, span)
251256
reraise(exc_type, exc_val, exc_tb)
252257
finally:
253-
try:
254-
del scope["datadog"]["request_span"]
255-
except KeyError:
256-
pass
257-
258+
if span in scope["datadog"]["request_spans"]:
259+
scope["datadog"]["request_spans"].remove(span)
258260
span.finish()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
fixes:
3+
- |
4+
asgi: Ensures ``error.message`` and ``error.stack`` tags are set when an exception is raised in a route.

tests/contrib/fastapi/test_fastapi.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import ddtrace
99
from ddtrace import config
10-
from ddtrace.constants import ERROR_MSG
1110
from ddtrace.contrib.fastapi import patch as fastapi_patch
1211
from ddtrace.contrib.fastapi import unpatch as fastapi_unpatch
1312
from ddtrace.contrib.starlette.patch import patch as patch_starlette
@@ -322,25 +321,10 @@ def test_invalid_path(client, tracer, test_spans):
322321
assert request_span.get_tag("component") == "fastapi"
323322

324323

325-
def test_500_error_raised(client, tracer, test_spans):
324+
@snapshot(ignores=["meta.error.stack"])
325+
def test_500_error_raised(snapshot_client):
326326
with pytest.raises(RuntimeError):
327-
client.get("/500", headers={"X-Token": "DataDog"})
328-
spans = test_spans.pop_traces()
329-
assert len(spans) == 1
330-
assert len(spans[0]) == 1
331-
332-
request_span = spans[0][0]
333-
assert request_span.service == "fastapi"
334-
assert request_span.name == "fastapi.request"
335-
assert request_span.resource == "GET /500"
336-
assert request_span.error == 1
337-
assert request_span.get_tag("http.method") == "GET"
338-
assert request_span.get_tag("http.url") == "http://testserver/500"
339-
assert request_span.get_tag("http.status_code") == "500"
340-
assert request_span.get_tag(ERROR_MSG) == "Server error"
341-
assert request_span.get_tag("error.type") == "builtins.RuntimeError"
342-
assert request_span.get_tag("component") == "fastapi"
343-
assert 'raise RuntimeError("Server error")' in request_span.get_tag("error.stack")
327+
snapshot_client.get("/500")
344328

345329

346330
def test_streaming_response(client, tracer, test_spans):
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[[
2+
{
3+
"name": "fastapi.request",
4+
"service": "fastapi",
5+
"resource": "GET /500",
6+
"trace_id": 0,
7+
"span_id": 1,
8+
"parent_id": 0,
9+
"type": "web",
10+
"error": 1,
11+
"meta": {
12+
"_dd.p.dm": "-0",
13+
"component": "fastapi",
14+
"error.message": "Server error",
15+
"error.stack": "Traceback (most recent call last):\n File \"/Users/munirabdinur/go/src/github.com/DataDog/dd-trace-py/ddtrace/contrib/asgi/middleware.py\", line 243, in __call__\n return await self.app(scope, receive, wrapped_send)\n File \"/Users/munirabdinur/go/src/github.com/DataDog/dd-trace-py/.riot/venv_py3913_fastapi~0640/lib/python3.9/site-packages/starlette/middleware/errors.py\", line 181, in __call__\n raise exc from None\n File \"/Users/munirabdinur/go/src/github.com/DataDog/dd-trace-py/.riot/venv_py3913_fastapi~0640/lib/python3.9/site-packages/starlette/middleware/errors.py\", line 159, in __call__\n await self.app(scope, receive, _send)\n File \"/Users/munirabdinur/go/src/github.com/DataDog/dd-trace-py/.riot/venv_py3913_fastapi~0640/lib/python3.9/site-packages/starlette/exceptions.py\", line 82, in __call__\n raise exc from None\n File \"/Users/munirabdinur/go/src/github.com/DataDog/dd-trace-py/.riot/venv_py3913_fastapi~0640/lib/python3.9/site-packages/starlette/exceptions.py\", line 71, in __call__\n await self.app(scope, receive, sender)\n File \"/Users/munirabdinur/go/src/github.com/DataDog/dd-trace-py/.riot/venv_py3913_fastapi~0640/lib/python3.9/site-packages/starlette/routing.py\", line 566, in __call__\n await route.handle(scope, receive, send)\n File \"/Users/munirabdinur/go/src/github.com/DataDog/dd-trace-py/.riot/venv_py3913_fastapi~0640/lib/python3.9/site-packages/starlette/routing.py\", line 227, in handle\n await self.app(scope, receive, send)\n File \"/Users/munirabdinur/go/src/github.com/DataDog/dd-trace-py/.riot/venv_py3913_fastapi~0640/lib/python3.9/site-packages/starlette/routing.py\", line 41, in app\n response = await func(request)\n File \"/Users/munirabdinur/go/src/github.com/DataDog/dd-trace-py/.riot/venv_py3913_fastapi~0640/lib/python3.9/site-packages/fastapi/routing.py\", line 201, in app\n raw_response = await run_endpoint_function(\n File \"/Users/munirabdinur/go/src/github.com/DataDog/dd-trace-py/.riot/venv_py3913_fastapi~0640/lib/python3.9/site-packages/fastapi/routing.py\", line 148, in run_endpoint_function\n return await dependant.call(**values)\n File \"/Users/munirabdinur/go/src/github.com/DataDog/dd-trace-py/tests/contrib/fastapi/app.py\", line 91, in error\n raise RuntimeError(\"Server error\")\nRuntimeError: Server error\n",
16+
"error.type": "builtins.RuntimeError",
17+
"http.method": "GET",
18+
"http.status_code": "500",
19+
"http.url": "http://testserver/500",
20+
"http.useragent": "testclient",
21+
"http.version": "1.1",
22+
"language": "python",
23+
"runtime-id": "64d5226c7c7c4f79a067f32ae2c114ef"
24+
},
25+
"metrics": {
26+
"_dd.agent_psr": 1.0,
27+
"_dd.top_level": 1,
28+
"_dd.tracer_kr": 1.0,
29+
"_sampling_priority_v1": 1,
30+
"process_id": 24153
31+
},
32+
"duration": 1430000,
33+
"start": 1683658745856878000
34+
}]]

0 commit comments

Comments
 (0)