Skip to content

feat(scope): Replace transaction with root_span #4263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 10, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
- `same_process_as_parent`
- `span_id`
- `parent_span_id`: you can supply a `parent_span` instead
- Setting `Scope.transaction` directly is no longer supported. Use `Scope.set_transaction_name()` instead.
- The `Scope.transaction` property has been removed. To obtain the root span (previously transaction), use `Scope.root_span`. To set the root span's (transaction's) name, use `Scope.set_transaction_name()`.
- Passing a list or `None` for `failed_request_status_codes` in the Starlette integration is no longer supported. Pass a set of integers instead.
- The `span` argument of `Scope.trace_propagation_meta` is no longer supported.
- Setting `Scope.user` directly is no longer supported. Use `Scope.set_user()` instead.
Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ def start_transaction(

def set_measurement(name, value, unit=""):
# type: (str, float, MeasurementUnit) -> None
transaction = get_current_scope().transaction
transaction = get_current_scope().root_span
if transaction is not None:
transaction.set_measurement(name, value, unit)

Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,7 @@ def _capture_experimental_log(self, current_scope, log):
log["attributes"]["sentry.trace.parent_span_id"] = span.span_id

if log.get("trace_id") is None:
transaction = current_scope.transaction
transaction = current_scope.root_span
propagation_context = isolation_scope.get_active_propagation_context()
if transaction is not None:
log["trace_id"] = transaction.trace_id
Expand Down
10 changes: 5 additions & 5 deletions sentry_sdk/integrations/arq.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,12 @@ def _capture_exception(exc_info):
# type: (ExcInfo) -> None
scope = sentry_sdk.get_current_scope()

if scope.transaction is not None:
if scope.root_span is not None:
if exc_info[0] in ARQ_CONTROL_FLOW_EXCEPTIONS:
scope.transaction.set_status(SPANSTATUS.ABORTED)
scope.root_span.set_status(SPANSTATUS.ABORTED)
return

scope.transaction.set_status(SPANSTATUS.INTERNAL_ERROR)
scope.root_span.set_status(SPANSTATUS.INTERNAL_ERROR)

event, hint = event_from_exception(
exc_info,
Expand All @@ -149,8 +149,8 @@ def event_processor(event, hint):

with capture_internal_exceptions():
scope = sentry_sdk.get_current_scope()
if scope.transaction is not None:
scope.transaction.name = ctx["job_name"]
if scope.root_span is not None:
scope.root_span.name = ctx["job_name"]
event["transaction"] = ctx["job_name"]

tags = event.setdefault("tags", {})
Expand Down
4 changes: 2 additions & 2 deletions sentry_sdk/integrations/django/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@ def wrap_async_view(callback):
async def sentry_wrapped_callback(request, *args, **kwargs):
# type: (Any, *Any, **Any) -> Any
current_scope = sentry_sdk.get_current_scope()
if current_scope.transaction is not None:
current_scope.transaction.update_active_thread()
if current_scope.root_span is not None:
current_scope.root_span.update_active_thread()

sentry_scope = sentry_sdk.get_isolation_scope()
if sentry_scope.profile is not None:
Expand Down
4 changes: 2 additions & 2 deletions sentry_sdk/integrations/django/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ def _wrap_sync_view(callback):
def sentry_wrapped_callback(request, *args, **kwargs):
# type: (Any, *Any, **Any) -> Any
current_scope = sentry_sdk.get_current_scope()
if current_scope.transaction is not None:
current_scope.transaction.update_active_thread()
if current_scope.root_span is not None:
current_scope.root_span.update_active_thread()

sentry_scope = sentry_sdk.get_isolation_scope()
# set the active thread id to the handler thread for sync views
Expand Down
4 changes: 2 additions & 2 deletions sentry_sdk/integrations/fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ def _sentry_get_request_handler(*args, **kwargs):
def _sentry_call(*args, **kwargs):
# type: (*Any, **Any) -> Any
current_scope = sentry_sdk.get_current_scope()
if current_scope.transaction is not None:
current_scope.transaction.update_active_thread()
if current_scope.root_span is not None:
current_scope.root_span.update_active_thread()

sentry_scope = sentry_sdk.get_isolation_scope()
if sentry_scope.profile is not None:
Expand Down
6 changes: 3 additions & 3 deletions sentry_sdk/integrations/huey.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ def _capture_exception(exc_info):
scope = sentry_sdk.get_current_scope()

if exc_info[0] in HUEY_CONTROL_FLOW_EXCEPTIONS:
scope.transaction.set_status(SPANSTATUS.ABORTED)
scope.root_span.set_status(SPANSTATUS.ABORTED)
return

scope.transaction.set_status(SPANSTATUS.INTERNAL_ERROR)
scope.root_span.set_status(SPANSTATUS.INTERNAL_ERROR)
event, hint = event_from_exception(
exc_info,
client_options=sentry_sdk.get_client().options,
Expand All @@ -136,7 +136,7 @@ def _sentry_execute(*args, **kwargs):
_capture_exception(exc_info)
reraise(*exc_info)
else:
sentry_sdk.get_current_scope().transaction.set_status(SPANSTATUS.OK)
sentry_sdk.get_current_scope().root_span.set_status(SPANSTATUS.OK)

return result

Expand Down
4 changes: 2 additions & 2 deletions sentry_sdk/integrations/quart.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ def decorator(old_func):
def _sentry_func(*args, **kwargs):
# type: (*Any, **Any) -> Any
current_scope = sentry_sdk.get_current_scope()
if current_scope.transaction is not None:
current_scope.transaction.update_active_thread()
if current_scope.root_span is not None:
current_scope.root_span.update_active_thread()

sentry_scope = sentry_sdk.get_isolation_scope()
if sentry_scope.profile is not None:
Expand Down
4 changes: 2 additions & 2 deletions sentry_sdk/integrations/starlette.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,8 @@ def _sentry_sync_func(*args, **kwargs):
return old_func(*args, **kwargs)

current_scope = sentry_sdk.get_current_scope()
if current_scope.transaction is not None:
current_scope.transaction.update_active_thread()
if current_scope.root_span is not None:
current_scope.root_span.update_active_thread()

sentry_scope = sentry_sdk.get_isolation_scope()
if sentry_scope.profile is not None:
Expand Down
4 changes: 2 additions & 2 deletions sentry_sdk/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -688,10 +688,10 @@ def fingerprint(self, value):
self._fingerprint = value

@property
def transaction(self):
def root_span(self):
# type: () -> Any
# would be type: () -> Optional[Span], see https://github.com/python/mypy/issues/3004
"""Return the transaction (root span) in the scope, if any."""
"""Return the root span in the scope, if any."""

# there is no span/transaction on the scope
if self._span is None:
Expand Down
15 changes: 15 additions & 0 deletions tests/test_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -915,3 +915,18 @@ def test_last_event_id_cleared(sentry_init):
Scope.get_isolation_scope().clear()

assert Scope.last_event_id() is None, "last_event_id should be cleared"


def test_root_span(sentry_init):
sentry_init(traces_sample_rate=1.0)

assert sentry_sdk.get_current_scope().root_span is None

with sentry_sdk.start_span(name="test") as root_span:
assert sentry_sdk.get_current_scope().root_span == root_span
with sentry_sdk.start_span(name="child"):
assert sentry_sdk.get_current_scope().root_span == root_span
with sentry_sdk.start_span(name="grandchild"):
assert sentry_sdk.get_current_scope().root_span == root_span

assert sentry_sdk.get_current_scope().root_span is None
Loading