Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sentry_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"get_isolation_scope",
"get_current_scope",
"get_current_span",
"get_current_streamed_span",
"get_traceparent",
"is_initialized",
"isolation_scope",
Expand Down
12 changes: 11 additions & 1 deletion sentry_sdk/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def overload(x: "T") -> "T":
"get_isolation_scope",
"get_current_scope",
"get_current_span",
"get_current_streamed_span",
"get_traceparent",
"is_initialized",
"isolation_scope",
Expand Down Expand Up @@ -422,13 +423,22 @@ def set_measurement(name: str, value: float, unit: "MeasurementUnit" = "") -> No

def get_current_span(
scope: "Optional[Scope]" = None,
) -> "Optional[Union[Span, StreamedSpan]]":
) -> "Optional[Span]":
"""
Returns the currently active span if there is one running, otherwise `None`
"""
return tracing_utils.get_current_span(scope)


def get_current_streamed_span(
scope: "Optional[Scope]" = None,
) -> "Optional[StreamedSpan]":
"""
Returns the currently active streamed span if there is one running, otherwise `None`
"""
return tracing_utils.get_current_streamed_span(scope)
Comment thread
alexander-alderman-webb marked this conversation as resolved.
Outdated
Copy link
Copy Markdown
Contributor

@sentrivana sentrivana Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd not expose this as a public API function, at least while the functionality is experimental. Maybe we can move this to traces.py, or at least prefix it with an underscore.

Would also be good to add more context to the docstring so that users know when this can be used (i.e., only if trace_lifecycle="stream").

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's a great point, it would be best to not expose too much. I've limited the API surface to _get_current_streamed_span in traces.py and expanded the docstring



Comment thread
alexander-alderman-webb marked this conversation as resolved.
Outdated
def get_traceparent() -> "Optional[str]":
"""
Returns the traceparent either from the active span or from the scope.
Expand Down
20 changes: 13 additions & 7 deletions sentry_sdk/integrations/celery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,16 @@ def setup_once() -> None:


def _set_status(status: str) -> None:
client = sentry_sdk.get_client()
span_streaming = has_span_streaming_enabled(client.options)

with capture_internal_exceptions():
scope = sentry_sdk.get_current_scope()
if scope.span is not None:
if isinstance(scope.span, Span):
scope.span.set_status(status)
else:
scope.span.status = "ok" if status == "ok" else "error"

if span_streaming and scope.streamed_span is not None:
scope.streamed_span.status = "ok" if status == "ok" else "error"
elif not span_streaming and scope.span is not None:
scope.span.set_status(status)


def _capture_exception(task: "Any", exc_info: "ExcInfo") -> None:
Expand Down Expand Up @@ -289,7 +292,10 @@ def apply_async(*args: "Any", **kwargs: "Any") -> "Any":

span_mgr: "Union[StreamedSpan, Span, NoOpMgr]" = NoOpMgr()
if span_streaming:
if not task_started_from_beat and sentry_sdk.get_current_span() is not None:
if (
not task_started_from_beat
and sentry_sdk.get_current_streamed_span() is not None
):
span_mgr = sentry_sdk.traces.start_span(
name=task_name,
attributes={
Expand Down Expand Up @@ -570,7 +576,7 @@ def sentry_publish(self: "Producer", *args: "Any", **kwargs: "Any") -> "Any":

span: "Union[StreamedSpan, Span, None]" = None
if span_streaming:
if sentry_sdk.get_current_span() is not None:
if sentry_sdk.get_current_streamed_span() is not None:
span = sentry_sdk.traces.start_span(
name=task_name,
attributes={
Expand Down
16 changes: 10 additions & 6 deletions sentry_sdk/integrations/fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,17 @@ def _sentry_get_request_handler(*args: "Any", **kwargs: "Any") -> "Any":
@wraps(old_call)
def _sentry_call(*args: "Any", **kwargs: "Any") -> "Any":
current_scope = sentry_sdk.get_current_scope()
current_span = current_scope.span

if isinstance(current_span, StreamedSpan) and not isinstance(
current_span, NoOpStreamedSpan
):
segment = current_span._segment
segment._update_active_thread()
client = sentry_sdk.get_client()
if has_span_streaming_enabled(client.options):
current_span = current_scope.streamed_span

if isinstance(current_span, StreamedSpan) and not isinstance(
current_span, NoOpStreamedSpan
):
segment = current_span._segment
segment._update_active_thread()

elif current_scope.transaction is not None:
current_scope.transaction.update_active_thread()

Expand Down
21 changes: 12 additions & 9 deletions sentry_sdk/integrations/starlette.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ def _default(value: "Any") -> "Any":
def _set_request_body_data_on_streaming_segment(
info: "Optional[Dict[str, Any]]",
) -> None:
current_span = sentry_sdk.get_current_span()
current_span = sentry_sdk.get_current_streamed_span()
if (
info
and "data" in info
Expand Down Expand Up @@ -545,19 +545,22 @@ def event_processor(

@functools.wraps(old_func)
def _sentry_sync_func(*args: "Any", **kwargs: "Any") -> "Any":
integration = sentry_sdk.get_client().get_integration(
StarletteIntegration
)
client = sentry_sdk.get_client()

integration = client.get_integration(StarletteIntegration)
if integration is None:
return old_func(*args, **kwargs)

current_scope = sentry_sdk.get_current_scope()
current_span = current_scope.span

if isinstance(current_span, StreamedSpan) and not isinstance(
current_span, NoOpStreamedSpan
):
current_span._segment._update_active_thread()
span_streaming = has_span_streaming_enabled(client.options)
if span_streaming:
current_span = current_scope.streamed_span

if isinstance(current_span, StreamedSpan) and not isinstance(
current_span, NoOpStreamedSpan
):
current_span._segment._update_active_thread()
elif current_scope.transaction is not None:
current_scope.transaction.update_active_thread()
Comment thread
sentry-warden[bot] marked this conversation as resolved.

Expand Down
45 changes: 34 additions & 11 deletions sentry_sdk/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -580,12 +580,18 @@ def get_traceparent(self, *args: "Any", **kwargs: "Any") -> "Optional[str]":
"""
client = self.get_client()

if not has_tracing_enabled(client.options):
return self.get_active_propagation_context().to_traceparent()
Comment thread
sentrivana marked this conversation as resolved.

span_streaming = has_span_streaming_enabled(client.options)
# If we have an active span, return traceparent from there
if (
has_tracing_enabled(client.options)
and self.span is not None
and not isinstance(self.span, NoOpStreamedSpan)
span_streaming
and self.streamed_span is not None
and not isinstance(self.streamed_span, NoOpStreamedSpan)
):
return self.streamed_span._to_traceparent()
elif not span_streaming and self.span is not None:
return self.span._to_traceparent()

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

if not has_tracing_enabled(client.options):
return self.get_active_propagation_context().get_baggage()
Comment thread
sentrivana marked this conversation as resolved.

span_streaming = has_span_streaming_enabled(client.options)
# If we have an active span, return baggage from there
if (
has_tracing_enabled(client.options)
and self.span is not None
and not isinstance(self.span, NoOpStreamedSpan)
span_streaming
and self.streamed_span is not None
and not isinstance(self.streamed_span, NoOpStreamedSpan)
):
return self.streamed_span._to_baggage()
elif not span_streaming and self.span is not None:
return self.span._to_baggage()

# else return baggage from the propagation context
Expand Down Expand Up @@ -680,7 +692,9 @@ def iter_trace_propagation_headers(
return

span = kwargs.pop("span", None)
span = span or self.span
if not span:
span_streaming = has_span_streaming_enabled(client.options)
span = self.streamed_span if span_streaming else self.span

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

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

@span.setter
def span(self, span: "Optional[Union[Span, StreamedSpan]]") -> None:
def span(self, span: "Optional[Span]") -> None:
self._span = span
# XXX: this differs from the implementation in JS, there Scope.setSpan
# does not set Scope._transactionName.
Expand All @@ -893,6 +907,15 @@ def span(self, span: "Optional[Union[Span, StreamedSpan]]") -> None:
if transaction.source:
self._transaction_info["source"] = transaction.source
Comment thread
alexander-alderman-webb marked this conversation as resolved.

@property
def streamed_span(self) -> "Optional[StreamedSpan]":
"""Get/set current tracing span."""
return self._span if isinstance(self._span, StreamedSpan) else None

@streamed_span.setter
def streamed_span(self, span: "Optional[StreamedSpan]") -> None:
self._span = span
Comment thread
alexander-alderman-webb marked this conversation as resolved.

# Also set _transaction and _transaction_info in streaming mode as this
# is used for populating events and linking them to segments
if (
Expand Down Expand Up @@ -1267,7 +1290,7 @@ def start_streamed_span(
if parent_span is _DEFAULT_PARENT_SPAN or isinstance(
parent_span, NoOpStreamedSpan
):
parent_span = self.span # type: ignore
parent_span = self.streamed_span

# If no eligible parent_span was provided and there is no currently
# active span, this is a segment
Expand Down
12 changes: 6 additions & 6 deletions sentry_sdk/traces.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,8 @@ def finish(self, end_timestamp: "Optional[Union[float, datetime]]" = None) -> No

def _start(self) -> None:
if self._active:
old_span = self._scope.span
self._scope.span = self
old_span = self._scope.streamed_span
self._scope.streamed_span = self
self._previous_span_on_scope = old_span

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

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

old_span = self._scope.span
self._scope.span = self
old_span = self._scope.streamed_span
self._scope.streamed_span = self
self._previous_span_on_scope = old_span

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

self._finished = True

Expand Down
13 changes: 12 additions & 1 deletion sentry_sdk/tracing_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1194,7 +1194,7 @@ def sync_wrapper(*args: "Any", **kwargs: "Any") -> "Any":

def get_current_span(
scope: "Optional[sentry_sdk.Scope]" = None,
) -> "Optional[Union[Span, StreamedSpan]]":
) -> "Optional[Span]":
"""
Returns the currently active span if there is one running, otherwise `None`
"""
Expand All @@ -1203,6 +1203,17 @@ def get_current_span(
return current_span


def get_current_streamed_span(
Comment thread
alexander-alderman-webb marked this conversation as resolved.
Outdated
scope: "Optional[sentry_sdk.Scope]" = None,
) -> "Optional[StreamedSpan]":
"""
Returns the currently active span if there is one running, otherwise `None`
"""
scope = scope or sentry_sdk.get_current_scope()
current_span = scope.streamed_span
return current_span


Comment thread
alexander-alderman-webb marked this conversation as resolved.
Outdated
def set_span_errored(span: "Optional[Union[Span, StreamedSpan]]" = None) -> None:
"""
Set the status of the current or given span to INTERNAL_ERROR.
Expand Down
8 changes: 6 additions & 2 deletions tests/integrations/celery/test_celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from celery.bin import worker

import sentry_sdk
from sentry_sdk import start_transaction, get_current_span
from sentry_sdk import start_transaction, get_current_span, get_current_streamed_span
from sentry_sdk.integrations.celery import (
CeleryIntegration,
_wrap_task_run,
Expand Down Expand Up @@ -663,7 +663,11 @@ def test_sentry_propagate_traces_override(span_streaming, init_celery):

@celery.task(name="dummy_task", bind=True)
def dummy_task(self, message):
trace_id = get_current_span().trace_id
trace_id = (
get_current_streamed_span().trace_id
if span_streaming
else get_current_span().trace_id
)
return trace_id

if span_streaming:
Expand Down
4 changes: 3 additions & 1 deletion tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
get_isolation_scope,
)

from sentry_sdk.tracing import Span

from sentry_sdk.client import Client, NonRecordingClient
from tests.conftest import TestTransportWithOptions

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

scope = get_current_scope()
fake_span = mock.MagicMock()
fake_span = Span()
scope.span = fake_span

assert get_current_span() == fake_span
Expand Down
Loading