Skip to content

Commit 9084c9a

Browse files
feat(django): Support span streaming (#6248)
Drop `django.function_name` and `context` attributes in streaming lifecycle mode. Replace deprecated `db.operation`, `db.system` and `db.name` attributes with `db.operation.name`, `db.system.name` and `db.namespace` attributes in the streaming lifecycle mode. Use `middleware.name` and `code.function.name` instead of `django.middleware_name` and `signal` in the streaming mode.
1 parent 882d364 commit 9084c9a

20 files changed

Lines changed: 5456 additions & 1959 deletions

sentry_sdk/integrations/asyncpg.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from sentry_sdk.tracing_utils import (
1313
add_query_source,
1414
has_span_streaming_enabled,
15-
record_sql_queries_supporting_streaming,
15+
record_sql_queries,
1616
)
1717
from sentry_sdk.utils import (
1818
capture_internal_exceptions,
@@ -82,7 +82,7 @@ async def _inner(*args: "Any", **kwargs: "Any") -> "T":
8282
return await f(*args, **kwargs)
8383

8484
query = _normalize_query(args[1])
85-
with record_sql_queries_supporting_streaming(
85+
with record_sql_queries(
8686
cursor=None,
8787
query=query,
8888
params_list=None,
@@ -123,7 +123,7 @@ def _record(
123123
param_style = "pyformat" if params_list else None
124124

125125
query = _normalize_query(query)
126-
with record_sql_queries_supporting_streaming(
126+
with record_sql_queries(
127127
cursor=cursor,
128128
query=query,
129129
params_list=params_list,
@@ -170,7 +170,7 @@ async def _inner(*args: "Any", **kwargs: "Any") -> "T":
170170

171171
cursor = args[0]
172172
query = _normalize_query(cursor._query)
173-
with record_sql_queries_supporting_streaming(
173+
with record_sql_queries(
174174
cursor=cursor,
175175
query=query,
176176
params_list=None,

sentry_sdk/integrations/django/__init__.py

Lines changed: 104 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,13 @@
1515
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
1616
from sentry_sdk.scope import add_global_event_processor, should_send_default_pii
1717
from sentry_sdk.serializer import add_global_repr_processor, add_repr_sequence_type
18+
from sentry_sdk.traces import StreamedSpan
1819
from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource
19-
from sentry_sdk.tracing_utils import add_query_source, record_sql_queries
20+
from sentry_sdk.tracing_utils import (
21+
add_query_source,
22+
has_span_streaming_enabled,
23+
record_sql_queries,
24+
)
2025
from sentry_sdk.utils import (
2126
CONTEXTVARS_ERROR_MESSAGE,
2227
HAS_REAL_CONTEXTVARS,
@@ -82,6 +87,7 @@
8287

8388
from sentry_sdk._types import Event, EventProcessor, Hint, NotImplementedType
8489
from sentry_sdk.integrations.wsgi import _ScopedResponse
90+
from sentry_sdk.traces import StreamedSpan
8591
from sentry_sdk.tracing import Span
8692

8793

@@ -639,8 +645,13 @@ def execute(
639645
_set_db_data(span, self)
640646
result = real_execute(self, sql, params)
641647

642-
with capture_internal_exceptions():
643-
add_query_source(span)
648+
if isinstance(span, StreamedSpan):
649+
with capture_internal_exceptions():
650+
add_query_source(span)
651+
652+
if not isinstance(span, StreamedSpan):
653+
with capture_internal_exceptions():
654+
add_query_source(span)
644655

645656
return result
646657

@@ -660,8 +671,13 @@ def executemany(
660671

661672
result = real_executemany(self, sql, param_list)
662673

663-
with capture_internal_exceptions():
664-
add_query_source(span)
674+
if isinstance(span, StreamedSpan):
675+
with capture_internal_exceptions():
676+
add_query_source(span)
677+
678+
if not isinstance(span, StreamedSpan):
679+
with capture_internal_exceptions():
680+
add_query_source(span)
665681

666682
return result
667683

@@ -670,41 +686,77 @@ def connect(self: "BaseDatabaseWrapper") -> None:
670686
with capture_internal_exceptions():
671687
sentry_sdk.add_breadcrumb(message="connect", category="query")
672688

673-
with sentry_sdk.start_span(
674-
op=OP.DB,
675-
name="connect",
676-
origin=DjangoIntegration.origin_db,
677-
) as span:
678-
_set_db_data(span, self)
679-
return real_connect(self)
689+
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
690+
if span_streaming:
691+
with sentry_sdk.traces.start_span(
692+
name="connect",
693+
attributes={
694+
"sentry.op": OP.DB,
695+
"sentry.origin": DjangoIntegration.origin_db,
696+
},
697+
) as span:
698+
_set_db_data(span, self)
699+
return real_connect(self)
700+
else:
701+
with sentry_sdk.start_span(
702+
op=OP.DB,
703+
name="connect",
704+
origin=DjangoIntegration.origin_db,
705+
) as span:
706+
_set_db_data(span, self)
707+
return real_connect(self)
680708

681709
def _commit(self: "BaseDatabaseWrapper") -> None:
682710
integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
683711

684712
if integration is None or not integration.db_transaction_spans:
685713
return real_commit(self)
686714

687-
with sentry_sdk.start_span(
688-
op=OP.DB,
689-
name=SPANNAME.DB_COMMIT,
690-
origin=DjangoIntegration.origin_db,
691-
) as span:
692-
_set_db_data(span, self, SPANNAME.DB_COMMIT)
693-
return real_commit(self)
715+
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
716+
if span_streaming:
717+
with sentry_sdk.traces.start_span(
718+
name=SPANNAME.DB_COMMIT,
719+
attributes={
720+
"sentry.op": OP.DB,
721+
"sentry.origin": DjangoIntegration.origin_db,
722+
},
723+
) as span:
724+
_set_db_data(span, self, SPANNAME.DB_COMMIT)
725+
return real_commit(self)
726+
else:
727+
with sentry_sdk.start_span(
728+
op=OP.DB,
729+
name=SPANNAME.DB_COMMIT,
730+
origin=DjangoIntegration.origin_db,
731+
) as span:
732+
_set_db_data(span, self, SPANNAME.DB_COMMIT)
733+
return real_commit(self)
694734

695735
def _rollback(self: "BaseDatabaseWrapper") -> None:
696736
integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
697737

698738
if integration is None or not integration.db_transaction_spans:
699739
return real_rollback(self)
700740

701-
with sentry_sdk.start_span(
702-
op=OP.DB,
703-
name=SPANNAME.DB_ROLLBACK,
704-
origin=DjangoIntegration.origin_db,
705-
) as span:
706-
_set_db_data(span, self, SPANNAME.DB_ROLLBACK)
707-
return real_rollback(self)
741+
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
742+
if span_streaming:
743+
with sentry_sdk.traces.start_span(
744+
name=SPANNAME.DB_ROLLBACK,
745+
attributes={
746+
"sentry.op": OP.DB,
747+
"sentry.origin": DjangoIntegration.origin_db,
748+
},
749+
) as span:
750+
_set_db_data(span, self, SPANNAME.DB_ROLLBACK)
751+
return real_rollback(self)
752+
else:
753+
with sentry_sdk.start_span(
754+
op=OP.DB,
755+
name=SPANNAME.DB_ROLLBACK,
756+
origin=DjangoIntegration.origin_db,
757+
) as span:
758+
_set_db_data(span, self, SPANNAME.DB_ROLLBACK)
759+
return real_rollback(self)
708760

709761
CursorWrapper.execute = execute
710762
CursorWrapper.executemany = executemany
@@ -715,14 +767,22 @@ def _rollback(self: "BaseDatabaseWrapper") -> None:
715767

716768

717769
def _set_db_data(
718-
span: "Span", cursor_or_db: "Any", db_operation: "Optional[str]" = None
770+
span: "Union[Span, StreamedSpan]",
771+
cursor_or_db: "Any",
772+
db_operation: "Optional[str]" = None,
719773
) -> None:
720774
db = cursor_or_db.db if hasattr(cursor_or_db, "db") else cursor_or_db
721775
vendor = db.vendor
722-
span.set_data(SPANDATA.DB_SYSTEM, vendor)
776+
if isinstance(span, StreamedSpan):
777+
span.set_attribute(SPANDATA.DB_SYSTEM_NAME, vendor)
723778

724-
if db_operation is not None:
725-
span.set_data(SPANDATA.DB_OPERATION, db_operation)
779+
if db_operation is not None:
780+
span.set_attribute(SPANDATA.DB_OPERATION_NAME, db_operation)
781+
else:
782+
span.set_data(SPANDATA.DB_SYSTEM, vendor)
783+
784+
if db_operation is not None:
785+
span.set_data(SPANDATA.DB_OPERATION, db_operation)
726786

727787
# Some custom backends override `__getattr__`, making it look like `cursor_or_db`
728788
# actually has a `connection` and the `connection` has a `get_dsn_parameters`
@@ -754,20 +814,29 @@ def _set_db_data(
754814
connection_params = db.get_connection_params()
755815

756816
db_name = connection_params.get("dbname") or connection_params.get("database")
757-
if db_name is not None:
758-
span.set_data(SPANDATA.DB_NAME, db_name)
817+
818+
if isinstance(span, StreamedSpan):
819+
if db_name is not None:
820+
span.set_attribute(SPANDATA.DB_NAMESPACE, db_name)
821+
822+
set_on_span = span.set_attribute
823+
else:
824+
if db_name is not None:
825+
span.set_data(SPANDATA.DB_NAME, db_name)
826+
827+
set_on_span = span.set_data
759828

760829
server_address = connection_params.get("host")
761830
if server_address is not None:
762-
span.set_data(SPANDATA.SERVER_ADDRESS, server_address)
831+
set_on_span(SPANDATA.SERVER_ADDRESS, server_address)
763832

764833
server_port = connection_params.get("port")
765834
if server_port is not None:
766-
span.set_data(SPANDATA.SERVER_PORT, str(server_port))
835+
set_on_span(SPANDATA.SERVER_PORT, str(server_port))
767836

768837
server_socket_address = connection_params.get("unix_socket")
769838
if server_socket_address is not None:
770-
span.set_data(SPANDATA.SERVER_SOCKET_ADDRESS, server_socket_address)
839+
set_on_span(SPANDATA.SERVER_SOCKET_ADDRESS, server_socket_address)
771840

772841

773842
def add_template_context_repr_sequence() -> None:

sentry_sdk/integrations/django/asgi.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from sentry_sdk.consts import OP
1818
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
1919
from sentry_sdk.scope import should_send_default_pii
20+
from sentry_sdk.traces import StreamedSpan
21+
from sentry_sdk.tracing_utils import has_span_streaming_enabled
2022
from sentry_sdk.utils import (
2123
capture_internal_exceptions,
2224
ensure_integration_enabled,
@@ -168,24 +170,42 @@ def wrap_async_view(callback: "Any") -> "Any":
168170
async def sentry_wrapped_callback(
169171
request: "Any", *args: "Any", **kwargs: "Any"
170172
) -> "Any":
173+
client = sentry_sdk.get_client()
174+
span_streaming = has_span_streaming_enabled(client.options)
171175
current_scope = sentry_sdk.get_current_scope()
172-
if current_scope.transaction is not None:
173-
current_scope.transaction.update_active_thread()
176+
if span_streaming:
177+
current_span = current_scope.streamed_span
178+
if type(current_span) is StreamedSpan:
179+
segment = current_span._segment
180+
segment._update_active_thread()
181+
else:
182+
if current_scope.transaction is not None:
183+
current_scope.transaction.update_active_thread()
174184

175185
sentry_scope = sentry_sdk.get_isolation_scope()
176186
if sentry_scope.profile is not None:
177187
sentry_scope.profile.update_active_thread_id()
178188

179-
integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
189+
integration = client.get_integration(DjangoIntegration)
180190
if not integration or not integration.middleware_spans:
181191
return await callback(request, *args, **kwargs)
182192

183-
with sentry_sdk.start_span(
184-
op=OP.VIEW_RENDER,
185-
name=request.resolver_match.view_name,
186-
origin=DjangoIntegration.origin,
187-
):
188-
return await callback(request, *args, **kwargs)
193+
if span_streaming:
194+
with sentry_sdk.traces.start_span(
195+
name=request.resolver_match.view_name,
196+
attributes={
197+
"sentry.op": OP.VIEW_RENDER,
198+
"sentry.origin": DjangoIntegration.origin,
199+
},
200+
):
201+
return await callback(request, *args, **kwargs)
202+
else:
203+
with sentry_sdk.start_span(
204+
op=OP.VIEW_RENDER,
205+
name=request.resolver_match.view_name,
206+
origin=DjangoIntegration.origin,
207+
):
208+
return await callback(request, *args, **kwargs)
189209

190210
return sentry_wrapped_callback
191211

0 commit comments

Comments
 (0)