diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 1c0fa76fb0..6f0aeb4510 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -138,6 +138,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh - `span.containing_transaction` has been removed. Use `span.root_span` instead. - `continue_from_headers`, `continue_from_environ` and `from_traceparent` have been removed, please use top-level API `sentry_sdk.continue_trace` instead. - `PropagationContext` constructor no longer takes a `dynamic_sampling_context` but takes a `baggage` object instead. +- `ThreadingIntegration` no longer takes the `propagate_hub` argument. ### Deprecated diff --git a/sentry_sdk/integrations/opentelemetry/contextvars_context.py b/sentry_sdk/integrations/opentelemetry/contextvars_context.py index b66b10d18a..8025f26ba8 100644 --- a/sentry_sdk/integrations/opentelemetry/contextvars_context.py +++ b/sentry_sdk/integrations/opentelemetry/contextvars_context.py @@ -1,3 +1,6 @@ +from typing import cast, TYPE_CHECKING + +from opentelemetry.trace import set_span_in_context from opentelemetry.context import Context, get_value, set_value from opentelemetry.context.contextvars_context import ContextVarsRuntimeContext @@ -9,25 +12,50 @@ SENTRY_USE_ISOLATION_SCOPE_KEY, ) +if TYPE_CHECKING: + from typing import Optional + from sentry_sdk.integrations.opentelemetry.scope import PotelScope + class SentryContextVarsRuntimeContext(ContextVarsRuntimeContext): def attach(self, context): # type: (Context) -> object scopes = get_value(SENTRY_SCOPES_KEY, context) + should_fork_isolation_scope = context.pop( SENTRY_FORK_ISOLATION_SCOPE_KEY, False ) + should_fork_isolation_scope = cast("bool", should_fork_isolation_scope) + should_use_isolation_scope = context.pop(SENTRY_USE_ISOLATION_SCOPE_KEY, None) + should_use_isolation_scope = cast( + "Optional[PotelScope]", should_use_isolation_scope + ) + should_use_current_scope = context.pop(SENTRY_USE_CURRENT_SCOPE_KEY, None) + should_use_current_scope = cast( + "Optional[PotelScope]", should_use_current_scope + ) - if scopes and isinstance(scopes, tuple): + if scopes: + scopes = cast("tuple[PotelScope, PotelScope]", scopes) (current_scope, isolation_scope) = scopes else: current_scope = sentry_sdk.get_current_scope() isolation_scope = sentry_sdk.get_isolation_scope() + new_context = context + if should_use_current_scope: new_scope = should_use_current_scope + + # the main case where we use use_scope is for + # scope propagation in the ThreadingIntegration + # so we need to carry forward the span reference explicitly too + span = should_use_current_scope.span + if span: + new_context = set_span_in_context(span._otel_span, new_context) + else: new_scope = current_scope.fork() @@ -40,5 +68,5 @@ def attach(self, context): new_scopes = (new_scope, new_isolation_scope) - new_context = set_value(SENTRY_SCOPES_KEY, new_scopes, context) + new_context = set_value(SENTRY_SCOPES_KEY, new_scopes, new_context) return super().attach(new_context) diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py index 99bfc66611..33cdd0d0be 100644 --- a/sentry_sdk/integrations/threading.py +++ b/sentry_sdk/integrations/threading.py @@ -7,7 +7,6 @@ from sentry_sdk.utils import ( event_from_exception, capture_internal_exceptions, - logger, reraise, ) @@ -27,22 +26,10 @@ class ThreadingIntegration(Integration): identifier = "threading" - def __init__(self, propagate_hub=None, propagate_scope=True): - # type: (Optional[bool], bool) -> None - if propagate_hub is not None: - logger.warning( - "Deprecated: propagate_hub is deprecated. This will be removed in the future." - ) - - # Note: propagate_hub did not have any effect on propagation of scope data - # scope data was always propagated no matter what the value of propagate_hub was - # This is why the default for propagate_scope is True - + def __init__(self, propagate_scope=True): + # type: (bool) -> None self.propagate_scope = propagate_scope - if propagate_hub is not None: - self.propagate_scope = propagate_hub - @staticmethod def setup_once(): # type: () -> None @@ -99,7 +86,8 @@ def _run_old_run_func(): with sentry_sdk.use_scope(current_scope_to_use): return _run_old_run_func() else: - return _run_old_run_func() + with sentry_sdk.isolation_scope(): + return _run_old_run_func() return run # type: ignore diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 54e6fc8928..7083c3709c 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -26,6 +26,7 @@ SENTRY_TRACE_HEADER_NAME, NoOpSpan, Span, + POTelSpan, Transaction, ) from sentry_sdk.utils import ( @@ -669,7 +670,7 @@ def clear(self): self.clear_breadcrumbs() self._should_capture = True # type: bool - self._span = None # type: Optional[Span] + self._span = None # type: Optional[POTelSpan] self._session = None # type: Optional[Session] self._force_auto_session_tracking = None # type: Optional[bool] @@ -777,13 +778,13 @@ def set_user(self, value): @property def span(self): - # type: () -> Optional[Span] + # type: () -> Optional[POTelSpan] """Get current tracing span.""" return self._span @span.setter def span(self, span): - # type: (Optional[Span]) -> None + # type: (Optional[POTelSpan]) -> None """Set current tracing span.""" self._span = span diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 6728b9b4c9..7686dcf052 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -1280,11 +1280,11 @@ def __eq__(self, other): def __repr__(self): # type: () -> str return ( - "<%s(op=%r, description:%r, trace_id=%r, span_id=%r, parent_span_id=%r, sampled=%r, origin=%r)>" + "<%s(op=%r, name:%r, trace_id=%r, span_id=%r, parent_span_id=%r, sampled=%r, origin=%r)>" % ( self.__class__.__name__, self.op, - self.description, + self.name, self.trace_id, self.span_id, self.parent_span_id, diff --git a/tests/integrations/threading/test_threading.py b/tests/integrations/threading/test_threading.py index 0d14fae352..75b3b7eea1 100644 --- a/tests/integrations/threading/test_threading.py +++ b/tests/integrations/threading/test_threading.py @@ -35,11 +35,11 @@ def crash(): assert not events -@pytest.mark.parametrize("propagate_hub", (True, False)) -def test_propagates_hub(sentry_init, capture_events, propagate_hub): +@pytest.mark.parametrize("propagate_scope", (True, False)) +def test_propagates_scope(sentry_init, capture_events, propagate_scope): sentry_init( default_integrations=False, - integrations=[ThreadingIntegration(propagate_hub=propagate_hub)], + integrations=[ThreadingIntegration(propagate_scope=propagate_scope)], ) events = capture_events() @@ -65,25 +65,25 @@ def stage2(): assert exception["mechanism"]["type"] == "threading" assert not exception["mechanism"]["handled"] - if propagate_hub: + if propagate_scope: assert event["tags"]["stage1"] == "true" else: assert "stage1" not in event.get("tags", {}) -@pytest.mark.parametrize("propagate_hub", (True, False)) -def test_propagates_threadpool_hub(sentry_init, capture_events, propagate_hub): +@pytest.mark.parametrize("propagate_scope", (True, False)) +def test_propagates_threadpool_scope(sentry_init, capture_events, propagate_scope): sentry_init( traces_sample_rate=1.0, - integrations=[ThreadingIntegration(propagate_hub=propagate_hub)], + integrations=[ThreadingIntegration(propagate_scope=propagate_scope)], ) events = capture_events() def double(number): - with sentry_sdk.start_span(op="task", name=str(number)): + with sentry_sdk.start_span(op="task", name=str(number), only_if_parent=True): return number * 2 - with sentry_sdk.start_transaction(name="test_handles_threadpool"): + with sentry_sdk.start_span(name="test_handles_threadpool"): with futures.ThreadPoolExecutor(max_workers=1) as executor: tasks = [executor.submit(double, number) for number in [1, 2, 3, 4]] for future in futures.as_completed(tasks): @@ -91,7 +91,7 @@ def double(number): sentry_sdk.flush() - if propagate_hub: + if propagate_scope: assert len(events) == 1 (event,) = events assert event["spans"][0]["trace_id"] == event["spans"][1]["trace_id"]