Skip to content

Commit 98294ce

Browse files
fix: Introduce _get_current_streamed_span() to keep types backwards compatible (#6177)
Create streaming-lifecycle variants of the `tracing_utils.get_current_span()` function and the `Scope.span` property. The existing functions are narrowed to be backwards compatible for users of the transaction-based span lifecycle, allowing users to upgrade their SDK version without their type checkers raising errors. Note that this breaks all current uses of these functions when the streaming lifecycle option is enabled (which is experimental). All call sites internal to the SDK are updated. The `Scope.span` property returns `None` if a `StreamedSpan` is held, and `Scope.streamed_span` returns `None` if a `Span` is held on the scope.
1 parent 66b3c6b commit 98294ce

9 files changed

Lines changed: 115 additions & 54 deletions

File tree

sentry_sdk/api.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from sentry_sdk._init_implementation import init
77
from sentry_sdk.consts import INSTRUMENTER
88
from sentry_sdk.scope import Scope, _ScopeManager, new_scope, isolation_scope
9-
from sentry_sdk.traces import StreamedSpan
9+
from sentry_sdk.traces import StreamedSpan, _get_current_streamed_span
1010
from sentry_sdk.tracing import NoOpSpan, Transaction, trace
1111
from sentry_sdk.crons import monitor
1212

@@ -422,7 +422,7 @@ def set_measurement(name: str, value: float, unit: "MeasurementUnit" = "") -> No
422422

423423
def get_current_span(
424424
scope: "Optional[Scope]" = None,
425-
) -> "Optional[Union[Span, StreamedSpan]]":
425+
) -> "Optional[Span]":
426426
"""
427427
Returns the currently active span if there is one running, otherwise `None`
428428
"""
@@ -533,12 +533,7 @@ def update_current_span(
533533
attributes={"user_id": 123, "batch_size": 50}
534534
)
535535
"""
536-
current_span = get_current_span()
537-
538-
if current_span is None:
539-
return
540-
541-
if isinstance(current_span, StreamedSpan):
536+
if isinstance(_get_current_streamed_span(), StreamedSpan):
542537
warnings.warn(
543538
"The `update_current_span` API isn't available in streaming mode. "
544539
"Retrieve the current span with get_current_span() and use its API "
@@ -548,6 +543,11 @@ def update_current_span(
548543
)
549544
return
550545

546+
current_span = get_current_span()
547+
548+
if current_span is None:
549+
return
550+
551551
if op is not None:
552552
current_span.op = op
553553

sentry_sdk/integrations/celery/__init__.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from sentry_sdk.integrations.celery.utils import _now_seconds_since_epoch
1616
from sentry_sdk.integrations.logging import ignore_logger
1717
from sentry_sdk.scope import should_send_default_pii
18-
from sentry_sdk.traces import StreamedSpan
18+
from sentry_sdk.traces import StreamedSpan, _get_current_streamed_span
1919
from sentry_sdk.tracing import BAGGAGE_HEADER_NAME, Span, TransactionSource
2020
from sentry_sdk.tracing_utils import Baggage, has_span_streaming_enabled
2121
from sentry_sdk.utils import (
@@ -98,13 +98,16 @@ def setup_once() -> None:
9898

9999

100100
def _set_status(status: str) -> None:
101+
client = sentry_sdk.get_client()
102+
span_streaming = has_span_streaming_enabled(client.options)
103+
101104
with capture_internal_exceptions():
102105
scope = sentry_sdk.get_current_scope()
103-
if scope.span is not None:
104-
if isinstance(scope.span, Span):
105-
scope.span.set_status(status)
106-
else:
107-
scope.span.status = "ok" if status == "ok" else "error"
106+
107+
if span_streaming and scope.streamed_span is not None:
108+
scope.streamed_span.status = "ok" if status == "ok" else "error"
109+
elif not span_streaming and scope.span is not None:
110+
scope.span.set_status(status)
108111

109112

110113
def _capture_exception(task: "Any", exc_info: "ExcInfo") -> None:
@@ -289,7 +292,7 @@ def apply_async(*args: "Any", **kwargs: "Any") -> "Any":
289292

290293
span_mgr: "Union[StreamedSpan, Span, NoOpMgr]" = NoOpMgr()
291294
if span_streaming:
292-
if not task_started_from_beat and sentry_sdk.get_current_span() is not None:
295+
if not task_started_from_beat and _get_current_streamed_span() is not None:
293296
span_mgr = sentry_sdk.traces.start_span(
294297
name=task_name,
295298
attributes={
@@ -570,7 +573,7 @@ def sentry_publish(self: "Producer", *args: "Any", **kwargs: "Any") -> "Any":
570573

571574
span: "Union[StreamedSpan, Span, None]" = None
572575
if span_streaming:
573-
if sentry_sdk.get_current_span() is not None:
576+
if _get_current_streamed_span() is not None:
574577
span = sentry_sdk.traces.start_span(
575578
name=task_name,
576579
attributes={

sentry_sdk/integrations/fastapi.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,17 @@ def _sentry_get_request_handler(*args: "Any", **kwargs: "Any") -> "Any":
9090
@wraps(old_call)
9191
def _sentry_call(*args: "Any", **kwargs: "Any") -> "Any":
9292
current_scope = sentry_sdk.get_current_scope()
93-
current_span = current_scope.span
9493

95-
if isinstance(current_span, StreamedSpan) and not isinstance(
96-
current_span, NoOpStreamedSpan
97-
):
98-
segment = current_span._segment
99-
segment._update_active_thread()
94+
client = sentry_sdk.get_client()
95+
if has_span_streaming_enabled(client.options):
96+
current_span = current_scope.streamed_span
97+
98+
if isinstance(current_span, StreamedSpan) and not isinstance(
99+
current_span, NoOpStreamedSpan
100+
):
101+
segment = current_span._segment
102+
segment._update_active_thread()
103+
100104
elif current_scope.transaction is not None:
101105
current_scope.transaction.update_active_thread()
102106

sentry_sdk/integrations/starlette.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
)
2222
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
2323
from sentry_sdk.scope import should_send_default_pii
24-
from sentry_sdk.traces import NoOpStreamedSpan, StreamedSpan
24+
from sentry_sdk.traces import NoOpStreamedSpan, StreamedSpan, _get_current_streamed_span
2525
from sentry_sdk.tracing import (
2626
SOURCE_FOR_STYLE,
2727
TransactionSource,
@@ -254,7 +254,7 @@ def _default(value: "Any") -> "Any":
254254
def _set_request_body_data_on_streaming_segment(
255255
info: "Optional[Dict[str, Any]]",
256256
) -> None:
257-
current_span = sentry_sdk.get_current_span()
257+
current_span = _get_current_streamed_span()
258258
if (
259259
info
260260
and "data" in info
@@ -545,19 +545,22 @@ def event_processor(
545545

546546
@functools.wraps(old_func)
547547
def _sentry_sync_func(*args: "Any", **kwargs: "Any") -> "Any":
548-
integration = sentry_sdk.get_client().get_integration(
549-
StarletteIntegration
550-
)
548+
client = sentry_sdk.get_client()
549+
550+
integration = client.get_integration(StarletteIntegration)
551551
if integration is None:
552552
return old_func(*args, **kwargs)
553553

554554
current_scope = sentry_sdk.get_current_scope()
555-
current_span = current_scope.span
556555

557-
if isinstance(current_span, StreamedSpan) and not isinstance(
558-
current_span, NoOpStreamedSpan
559-
):
560-
current_span._segment._update_active_thread()
556+
span_streaming = has_span_streaming_enabled(client.options)
557+
if span_streaming:
558+
current_span = current_scope.streamed_span
559+
560+
if isinstance(current_span, StreamedSpan) and not isinstance(
561+
current_span, NoOpStreamedSpan
562+
):
563+
current_span._segment._update_active_thread()
561564
elif current_scope.transaction is not None:
562565
current_scope.transaction.update_active_thread()
563566

sentry_sdk/scope.py

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -580,12 +580,18 @@ def get_traceparent(self, *args: "Any", **kwargs: "Any") -> "Optional[str]":
580580
"""
581581
client = self.get_client()
582582

583+
if not has_tracing_enabled(client.options):
584+
return self.get_active_propagation_context().to_traceparent()
585+
586+
span_streaming = has_span_streaming_enabled(client.options)
583587
# If we have an active span, return traceparent from there
584588
if (
585-
has_tracing_enabled(client.options)
586-
and self.span is not None
587-
and not isinstance(self.span, NoOpStreamedSpan)
589+
span_streaming
590+
and self.streamed_span is not None
591+
and not isinstance(self.streamed_span, NoOpStreamedSpan)
588592
):
593+
return self.streamed_span._to_traceparent()
594+
elif not span_streaming and self.span is not None:
589595
return self.span._to_traceparent()
590596

591597
# else return traceparent from the propagation context
@@ -598,12 +604,18 @@ def get_baggage(self, *args: "Any", **kwargs: "Any") -> "Optional[Baggage]":
598604
"""
599605
client = self.get_client()
600606

607+
if not has_tracing_enabled(client.options):
608+
return self.get_active_propagation_context().get_baggage()
609+
610+
span_streaming = has_span_streaming_enabled(client.options)
601611
# If we have an active span, return baggage from there
602612
if (
603-
has_tracing_enabled(client.options)
604-
and self.span is not None
605-
and not isinstance(self.span, NoOpStreamedSpan)
613+
span_streaming
614+
and self.streamed_span is not None
615+
and not isinstance(self.streamed_span, NoOpStreamedSpan)
606616
):
617+
return self.streamed_span._to_baggage()
618+
elif not span_streaming and self.span is not None:
607619
return self.span._to_baggage()
608620

609621
# else return baggage from the propagation context
@@ -680,7 +692,9 @@ def iter_trace_propagation_headers(
680692
return
681693

682694
span = kwargs.pop("span", None)
683-
span = span or self.span
695+
if not span:
696+
span_streaming = has_span_streaming_enabled(client.options)
697+
span = self.streamed_span if span_streaming else self.span
684698

685699
if (
686700
has_tracing_enabled(client.options)
@@ -877,12 +891,12 @@ def set_user(self, value: "Optional[Dict[str, Any]]") -> None:
877891
session.update(user=value)
878892

879893
@property
880-
def span(self) -> "Optional[Union[Span, StreamedSpan]]":
894+
def span(self) -> "Optional[Span]":
881895
"""Get/set current tracing span or transaction."""
882-
return self._span
896+
return self._span if isinstance(self._span, Span) else None
883897

884898
@span.setter
885-
def span(self, span: "Optional[Union[Span, StreamedSpan]]") -> None:
899+
def span(self, span: "Optional[Span]") -> None:
886900
self._span = span
887901
# XXX: this differs from the implementation in JS, there Scope.setSpan
888902
# does not set Scope._transactionName.
@@ -893,6 +907,15 @@ def span(self, span: "Optional[Union[Span, StreamedSpan]]") -> None:
893907
if transaction.source:
894908
self._transaction_info["source"] = transaction.source
895909

910+
@property
911+
def streamed_span(self) -> "Optional[StreamedSpan]":
912+
"""Get/set current tracing span."""
913+
return self._span if isinstance(self._span, StreamedSpan) else None
914+
915+
@streamed_span.setter
916+
def streamed_span(self, span: "Optional[StreamedSpan]") -> None:
917+
self._span = span
918+
896919
# Also set _transaction and _transaction_info in streaming mode as this
897920
# is used for populating events and linking them to segments
898921
if (
@@ -1267,7 +1290,7 @@ def start_streamed_span(
12671290
if parent_span is _DEFAULT_PARENT_SPAN or isinstance(
12681291
parent_span, NoOpStreamedSpan
12691292
):
1270-
parent_span = self.span # type: ignore
1293+
parent_span = self.streamed_span
12711294

12721295
# If no eligible parent_span was provided and there is no currently
12731296
# active span, this is a segment

sentry_sdk/traces.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,8 @@ def finish(self, end_timestamp: "Optional[Union[float, datetime]]" = None) -> No
341341

342342
def _start(self) -> None:
343343
if self._active:
344-
old_span = self._scope.span
345-
self._scope.span = self
344+
old_span = self._scope.streamed_span
345+
self._scope.streamed_span = self
346346
self._previous_span_on_scope = old_span
347347

348348
def _end(self, end_timestamp: "Optional[Union[float, datetime]]" = None) -> None:
@@ -360,7 +360,7 @@ def _end(self, end_timestamp: "Optional[Union[float, datetime]]" = None) -> None
360360
with capture_internal_exceptions():
361361
old_span = self._previous_span_on_scope
362362
del self._previous_span_on_scope
363-
self._scope.span = old_span
363+
self._scope.streamed_span = old_span
364364

365365
# Set attributes from the segment. These are set on span end on purpose
366366
# so that we have the best chance to capture the segment's final name
@@ -586,8 +586,8 @@ def _start(self) -> None:
586586
if self._scope is None:
587587
return
588588

589-
old_span = self._scope.span
590-
self._scope.span = self
589+
old_span = self._scope.streamed_span
590+
self._scope.streamed_span = self
591591
self._previous_span_on_scope = old_span
592592

593593
def _end(self, end_timestamp: "Optional[Union[float, datetime]]" = None) -> None:
@@ -610,7 +610,7 @@ def _end(self, end_timestamp: "Optional[Union[float, datetime]]" = None) -> None
610610
with capture_internal_exceptions():
611611
old_span = self._previous_span_on_scope
612612
del self._previous_span_on_scope
613-
self._scope.span = old_span
613+
self._scope.streamed_span = old_span
614614

615615
self._finished = True
616616

@@ -755,3 +755,17 @@ def make_db_query(sql):
755755
return decorator(func)
756756
else:
757757
return decorator
758+
759+
760+
def _get_current_streamed_span(
761+
scope: "Optional[sentry_sdk.Scope]" = None,
762+
) -> "Optional[StreamedSpan]":
763+
"""
764+
Returns the currently active span on the scope if the span is a `StreamedSpan`, otherwise `None`.
765+
766+
This function will only return a non-`None` value when the streaming trace lifecycle is enabled.
767+
To enable the lifecycle, pass `_experiments={"trace_lifecycle": "stream"}` to `sentry.init()`.
768+
"""
769+
scope = scope or sentry_sdk.get_current_scope()
770+
current_span = scope.streamed_span
771+
return current_span

sentry_sdk/tracing_utils.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,7 +1194,7 @@ def sync_wrapper(*args: "Any", **kwargs: "Any") -> "Any":
11941194

11951195
def get_current_span(
11961196
scope: "Optional[sentry_sdk.Scope]" = None,
1197-
) -> "Optional[Union[Span, StreamedSpan]]":
1197+
) -> "Optional[Span]":
11981198
"""
11991199
Returns the currently active span if there is one running, otherwise `None`
12001200
"""
@@ -1208,9 +1208,16 @@ def set_span_errored(span: "Optional[Union[Span, StreamedSpan]]" = None) -> None
12081208
Set the status of the current or given span to INTERNAL_ERROR.
12091209
Also sets the status of the transaction (root span) to INTERNAL_ERROR.
12101210
"""
1211-
from sentry_sdk.traces import StreamedSpan, SpanStatus
1211+
from sentry_sdk.traces import StreamedSpan, SpanStatus, _get_current_streamed_span
12121212

1213-
span = span or get_current_span()
1213+
client = sentry_sdk.get_client()
1214+
1215+
if not span:
1216+
span = (
1217+
_get_current_streamed_span()
1218+
if has_span_streaming_enabled(client.options)
1219+
else sentry_sdk.get_current_span()
1220+
)
12141221

12151222
if span is not None:
12161223
if isinstance(span, Span):

tests/integrations/celery/test_celery.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import sentry_sdk
1010
from sentry_sdk import start_transaction, get_current_span
11+
from sentry_sdk.traces import _get_current_streamed_span
1112
from sentry_sdk.integrations.celery import (
1213
CeleryIntegration,
1314
_wrap_task_run,
@@ -663,7 +664,11 @@ def test_sentry_propagate_traces_override(span_streaming, init_celery):
663664

664665
@celery.task(name="dummy_task", bind=True)
665666
def dummy_task(self, message):
666-
trace_id = get_current_span().trace_id
667+
trace_id = (
668+
_get_current_streamed_span().trace_id
669+
if span_streaming
670+
else get_current_span().trace_id
671+
)
667672
return trace_id
668673

669674
if span_streaming:

tests/test_api.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
get_isolation_scope,
2222
)
2323

24+
from sentry_sdk.tracing import Span
25+
2426
from sentry_sdk.client import Client, NonRecordingClient
2527
from tests.conftest import TestTransportWithOptions
2628

@@ -40,7 +42,7 @@ def test_get_current_span_default_hub(sentry_init):
4042
assert get_current_span() is None
4143

4244
scope = get_current_scope()
43-
fake_span = mock.MagicMock()
45+
fake_span = Span()
4446
scope.span = fake_span
4547

4648
assert get_current_span() == fake_span

0 commit comments

Comments
 (0)