Skip to content

Commit 984b12b

Browse files
authored
fix(asgi): ensure error tags are set on request spans [backports #5818 to 1.11] (#5837)
#5818 ## Checklist - [x] Change(s) are motivated and described in the PR description. - [x] Testing strategy is described if automated tests are not included in the PR. - [x] Risk is outlined (performance impact, potential for breakage, maintainability, etc). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/contributing.html#Release-Note-Guidelines) are followed. - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)). - [x] OPTIONAL: PR description includes explicit acknowledgement of the performance implications of the change as reported in the benchmarks PR comment. ## Reviewer Checklist - [x] Title is accurate. - [x] No unnecessary changes are introduced. - [x] Description motivates each change. - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes unless absolutely necessary. - [x] Testing strategy adequately addresses listed risk(s). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] Release note makes sense to a user of the library. - [x] Reviewer has explicitly acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment.
1 parent 08e889a commit 984b12b

File tree

4 files changed

+53
-29
lines changed

4 files changed

+53
-29
lines changed

ddtrace/contrib/asgi/middleware.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -213,14 +213,19 @@ async def wrapped_send(message):
213213
trace_utils.set_http_meta(
214214
span, self.integration_config, status_code=status_code, response_headers=response_headers
215215
)
216-
217216
try:
218217
return await send(message)
219218
finally:
220219
# Per asgi spec, "more_body" is used if there is still data to send
221220
# Close the span if "http.response.body" has no more data left to send in the
222221
# response.
223-
if message.get("type") == "http.response.body" and not message.get("more_body", False):
222+
if (
223+
message.get("type") == "http.response.body"
224+
and not message.get("more_body", False)
225+
# If the span has an error status code delay finishing the span until the
226+
# traceback and exception message is available
227+
and span.error == 0
228+
):
224229
span.finish()
225230

226231
async def wrapped_blocked_send(message):
@@ -246,7 +251,7 @@ async def wrapped_blocked_send(message):
246251
try:
247252
return await send(message)
248253
finally:
249-
if message.get("type") == "http.response.body":
254+
if message.get("type") == "http.response.body" and span.error == 0:
250255
span.finish()
251256

252257
try:
@@ -259,9 +264,6 @@ async def wrapped_blocked_send(message):
259264
self.handle_exception_span(exc, span)
260265
reraise(exc_type, exc_val, exc_tb)
261266
finally:
262-
try:
263-
del scope["datadog"]["request_span"]
264-
except KeyError:
265-
pass
266-
267+
if span in scope["datadog"]["request_spans"]:
268+
scope["datadog"]["request_spans"].remove(span)
267269
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 & 21 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
@@ -341,27 +340,10 @@ def test_invalid_path(client, tracer, test_spans):
341340
assert request_span.get_tag("span.kind") == "server"
342341

343342

344-
def test_500_error_raised(client, tracer, test_spans):
343+
@snapshot(ignores=["meta.error.stack"])
344+
def test_500_error_raised(snapshot_client):
345345
with pytest.raises(RuntimeError):
346-
client.get("/500", headers={"X-Token": "DataDog"})
347-
spans = test_spans.pop_traces()
348-
assert len(spans) == 1
349-
assert len(spans[0]) == 1
350-
351-
request_span = spans[0][0]
352-
assert request_span.service == "fastapi"
353-
assert request_span.name == "fastapi.request"
354-
assert request_span.resource == "GET /500"
355-
assert request_span.get_tag("http.route") == "/500"
356-
assert request_span.error == 1
357-
assert request_span.get_tag("http.method") == "GET"
358-
assert request_span.get_tag("http.url") == "http://testserver/500"
359-
assert request_span.get_tag("http.status_code") == "500"
360-
assert request_span.get_tag(ERROR_MSG) == "Server error"
361-
assert request_span.get_tag("error.type") == "builtins.RuntimeError"
362-
assert request_span.get_tag("component") == "fastapi"
363-
assert request_span.get_tag("span.kind") == "server"
364-
assert 'raise RuntimeError("Server error")' in request_span.get_tag("error.stack")
346+
snapshot_client.get("/500")
365347

366348

367349
def test_streaming_response(client, tracer, test_spans):
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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.route": "/500",
19+
"http.status_code": "500",
20+
"http.url": "http://testserver/500",
21+
"http.useragent": "testclient",
22+
"http.version": "1.1",
23+
"language": "python",
24+
"runtime-id": "64d5226c7c7c4f79a067f32ae2c114ef",
25+
"span.kind": "server"
26+
},
27+
"metrics": {
28+
"_dd.agent_psr": 1.0,
29+
"_dd.top_level": 1,
30+
"_dd.tracer_kr": 1.0,
31+
"_sampling_priority_v1": 1,
32+
"process_id": 24153
33+
},
34+
"duration": 1430000,
35+
"start": 1683658745856878000
36+
}]]

0 commit comments

Comments
 (0)