feat(django): Support span streaming#6248
Conversation
Codecov Results 📊✅ 88 passed | Total: 88 | Pass Rate: 100% | Execution Time: 19.65s All tests are passing successfully. ❌ Patch coverage is 2.19%. Project has 16093 uncovered lines. Files with missing lines (13)
Generated by Codecov Action |
| span_origin: str = "manual", | ||
| ) -> "Generator[Union[sentry_sdk.tracing.Span, sentry_sdk.traces.StreamedSpan], None, None]": | ||
| # TODO: Bring back capturing of params by default | ||
| client = sentry_sdk.get_client() |
There was a problem hiding this comment.
add_query_source called after StreamedSpan is already captured in Django execute/executemany
In django/__init__.py execute (line 648) and executemany (line 669), add_query_source(span) is called outside the with record_sql_queries(...) block. For StreamedSpan, _end() → _capture_span() fires when the with block exits, so those source-code attributes (code.file.path, code.line.number, code.function) are set on an already-captured span and silently lost. The asyncpg integration correctly calls add_query_source inside the with block only when isinstance(span, StreamedSpan).
Verification
Traced: StreamedSpan.__exit__ → _end() → self._scope._capture_span(self) (traces.py ~412) serialises and queues the span. set_attribute after this point mutates the local dict but the span is already sent. add_query_source checks isinstance(span, Span): if span.timestamp is None: return but skips that guard for StreamedSpan, so it proceeds and calls span.set_attribute(...) on the finished span. asyncpg (_wrap_execute, _wrap_connection_method, _wrap_cursor_method) avoids this by conditionally calling add_query_source inside the with block when isinstance(span, StreamedSpan). Django execute/executemany lack this conditional, so query source attributes are always dropped in streaming mode.
Identified by Warden find-bugs · YUR-B3G
|
|
||
| assert spans[4]["attributes"][SPANDATA.HTTP_REQUEST_METHOD] == "OPTIONS" |
There was a problem hiding this comment.
Contradictory hardcoded span indices in test_transaction_http_method_custom streaming path
The assertions spans[4][...] == "OPTIONS" and spans[7][...] == "HEAD" are mutually inconsistent: the first implies OPTIONS produces 5 spans (root at index 4), while the second implies OPTIONS produces only 3 spans (root at index 7 = 5+2). Both requests hit the same /nomessage view with the same integration config (no middleware/cache/signal spans), so they must produce equal span counts — making at least one of these index assertions check the wrong span or raise IndexError.
Verification
GET is excluded from capture (only OPTIONS/HEAD are captured), so spans contains only spans from the OPTIONS and HEAD requests. Both go to the same nomessage view (trivially returns HttpResponse). DjangoIntegration() uses middleware_spans=False (default). If each request produces N spans (root last), OPTIONS root is at index N-1 and HEAD root is at index 2N-1. Solving: N-1=4 gives N=5; 2N-1=7 gives N=4. These are contradictory — N cannot simultaneously be 4 and 5. At least one assertion must be checking the wrong span.
Identified by Warden find-bugs · RT7-BXG
Description
In the streaming path, use
db.namespaceinstead ofdb.namedb.system.nameinstead ofdb.systemdb.operation.nameinstead ofdb.operationmiddleware.nameinstead ofdjango.middleware_namecode.function.nameinstead ofsignalcode.filepathinstead ofcode.file.pathcode.line.numberinstead ofcode.linenoDropped attributes:
django.function_namecontextAdapting Tests
sedcommands used for converting transaction context managers:sed commands used for converting specific attributes:
sedcommands used for converting event capture:sedcommands used for convertingop:sedcommands used for converting origin:sedcommands used for convertingdescription:sedcommands used for convertingdatatoattributes:sedcommands for converting trace id:sedcommands used for converting timestamps:other test changes:
Issues
Closes #6015
Reminders
tox -e linters.feat:,fix:,ref:,meta:)