diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 09c580defa..035afaf82b 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -925,12 +925,19 @@ def streamed_span(self, span: "Optional[StreamedSpan]") -> None: # Also set _transaction and _transaction_info in streaming mode as this # is used for populating events and linking them to segments - if type(span) is StreamedSpan and span._is_segment(): + if not isinstance(span, StreamedSpan) or not span._is_segment(): + return + + if type(span) is StreamedSpan: self._transaction = span.name if span._attributes.get("sentry.segment.name.source"): self._transaction_info["source"] = str( span._attributes["sentry.segment.name.source"] ) + return + + if type(span) is NoOpStreamedSpan and span._name is not None: + self._transaction = span.name @property def profile(self) -> "Optional[Profile]": @@ -1309,6 +1316,7 @@ def start_streamed_span( if is_ignored_span(name, attributes): return NoOpStreamedSpan( + name=name, scope=self, segment=None, trace_id=propagation_context.trace_id, @@ -1329,6 +1337,7 @@ def start_streamed_span( if sampled is False or sampled is None: return NoOpStreamedSpan( + name=name, scope=self, segment=None, trace_id=propagation_context.trace_id, @@ -1359,6 +1368,7 @@ def start_streamed_span( with new_scope(): if is_ignored_span(name, attributes): return NoOpStreamedSpan( + name=name, segment=parent_span._segment, trace_id=parent_span.trace_id, parent_span_id=parent_span.span_id, @@ -1368,6 +1378,7 @@ def start_streamed_span( if isinstance(parent_span, NoOpStreamedSpan): return NoOpStreamedSpan( + name=name, segment=parent_span._segment, trace_id=parent_span.trace_id, parent_span_id=parent_span.span_id, diff --git a/sentry_sdk/traces.py b/sentry_sdk/traces.py index d8251a9101..904a40bbc4 100644 --- a/sentry_sdk/traces.py +++ b/sentry_sdk/traces.py @@ -632,6 +632,7 @@ class NoOpStreamedSpan(StreamedSpan): def __init__( self, + name: "Optional[str]" = None, segment: "Optional[StreamedSpan]" = None, trace_id: "Optional[str]" = None, parent_span_id: "Optional[str]" = None, @@ -643,6 +644,8 @@ def __init__( sample_rand: "Optional[float]" = None, sample_rate: "Optional[float]" = None, ) -> None: + self._name = name # type: ignore[assignment] + self._span_id: "Optional[str]" = None self._sampled = sampled @@ -739,11 +742,11 @@ def status(self, status: "Union[SpanStatus, str]") -> None: @property def name(self) -> str: - return "" + return self._name or "" @name.setter - def name(self, value: str) -> None: - pass + def name(self, name: str) -> None: + self._name = name @property def active(self) -> bool: diff --git a/tests/integrations/django/test_basic.py b/tests/integrations/django/test_basic.py index 93b5477010..03e62fe6ec 100644 --- a/tests/integrations/django/test_basic.py +++ b/tests/integrations/django/test_basic.py @@ -1293,6 +1293,56 @@ def test_transaction_style( assert event["transaction"] == expected_transaction +@pytest.mark.parametrize( + "transaction_style,client_url,expected_transaction,expected_source,expected_response", + [ + ( + "function_name", + "/message", + "tests.integrations.django.myapp.views.message", + "component", + b"ok", + ), + ("url", "/message", "/message", "route", b"ok"), + ("url", "/404", "/404", "url", b"404"), + ], +) +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_transaction_style_tracing_disabled( + sentry_init, + client, + capture_events, + capture_items, + transaction_style, + client_url, + expected_transaction, + expected_source, + expected_response, + span_streaming, +): + sentry_init( + integrations=[DjangoIntegration(transaction_style=transaction_style)], + send_default_pii=True, + trace_lifecycle="stream" if span_streaming else "static", + ) + if span_streaming: + items = capture_items("event") + + content, status, headers = unpack_werkzeug_response(client.get(client_url)) + assert content == expected_response + + (event,) = (item.payload for item in items if item.type == "event") + else: + events = capture_events() + + content, status, headers = unpack_werkzeug_response(client.get(client_url)) + assert content == expected_response + + (event,) = events + + assert event["transaction"] == expected_transaction + + @pytest.mark.parametrize("span_streaming", [True, False]) def test_request_body( sentry_init, diff --git a/tests/tracing/test_integration_tests.py b/tests/tracing/test_integration_tests.py index 8bc380b8b6..b293d36134 100644 --- a/tests/tracing/test_integration_tests.py +++ b/tests/tracing/test_integration_tests.py @@ -91,6 +91,21 @@ def test_basic_span_streaming(sentry_init, capture_items, sample_rate): assert not items +def test_error_event_linked_without_performance_span_streaming( + sentry_init, capture_items +): + sentry_init(traces_sample_rate=None, trace_lifecycle="stream") + items = capture_items("event") + + with sentry_sdk.traces.start_span(name="no-op span"): + sentry_sdk.capture_message("hi") + + sentry_sdk.flush() + + (event,) = (item.payload for item in items) + assert event["transaction"] == "no-op span" + + @pytest.mark.parametrize("parent_sampled", [True, False, None]) @pytest.mark.parametrize("sample_rate", [0.0, 1.0]) def test_continue_trace(sentry_init, capture_envelopes, parent_sampled, sample_rate): diff --git a/tests/tracing/test_span_streaming.py b/tests/tracing/test_span_streaming.py index 3800a4badc..81cbeec7fb 100644 --- a/tests/tracing/test_span_streaming.py +++ b/tests/tracing/test_span_streaming.py @@ -799,7 +799,7 @@ def test_continue_trace_unsampled(sentry_init, capture_items): ... assert span.sampled is False - assert span.name == "" + assert span.name == "segment" assert span.trace_id == trace_id assert span.span_id != "0000000000000000"