diff --git a/sentry_sdk/integrations/opentelemetry/scope.py b/sentry_sdk/integrations/opentelemetry/scope.py index 11714fda53..98be716346 100644 --- a/sentry_sdk/integrations/opentelemetry/scope.py +++ b/sentry_sdk/integrations/opentelemetry/scope.py @@ -11,6 +11,7 @@ use_span, ) +from sentry_sdk.client import NonRecordingClient from sentry_sdk.integrations.opentelemetry.consts import ( SENTRY_SCOPES_KEY, SENTRY_FORK_ISOLATION_SCOPE_KEY, @@ -26,10 +27,18 @@ from typing import Tuple, Optional, Generator, Dict, Any from typing_extensions import Unpack + import sentry_sdk from sentry_sdk._types import SamplingContext from sentry_sdk.tracing import TransactionKwargs +# Holds data that will be added to **all** events sent by this process. +# In case this is a http server (think web framework) with multiple users +# the data will be added to events of all users. +# Typically this is used for process wide data such as the release. +_global_scope = None # type: Optional[Scope] + + class PotelScope(Scope): @classmethod def _get_scopes(cls): @@ -73,6 +82,59 @@ def _get_isolation_scope(cls): scopes = cls._get_scopes() return scopes[1] if scopes else None + @classmethod + def get_global_scope(cls): + # type: () -> Scope + """ + .. versionadded:: 2.0.0 + + Returns the global scope. + """ + global _global_scope + if _global_scope is None: + _global_scope = PotelScope(ty=ScopeType.GLOBAL) + + return _global_scope + + @classmethod + def get_client(cls): + # type: () -> sentry_sdk.client.BaseClient + """ + .. versionadded:: 2.0.0 + + Returns the currently used :py:class:`sentry_sdk.Client`. + This checks the current scope, the isolation scope and the global scope for a client. + If no client is available a :py:class:`sentry_sdk.client.NonRecordingClient` is returned. + """ + scopes = cls._get_scopes() + if scopes: + current_scope, isolation_scope = scopes + try: + client = current_scope.client + except AttributeError: + client = None + + if client is not None and client.is_active(): + return client + + try: + client = isolation_scope.client + except AttributeError: + client = None + + if client is not None and client.is_active(): + return client + + try: + client = _global_scope.client # type: ignore + except AttributeError: + client = None + + if client is not None and client.is_active(): + return client + + return NonRecordingClient() + @contextmanager def continue_trace(self, environ_or_headers): # type: (Dict[str, Any]) -> Generator[None, None, None] diff --git a/tests/test_scope.py b/tests/test_scope.py index 0dfa155d11..f437b07ba5 100644 --- a/tests/test_scope.py +++ b/tests/test_scope.py @@ -10,8 +10,8 @@ new_scope, ) from sentry_sdk.client import Client, NonRecordingClient +from sentry_sdk.integrations.opentelemetry.scope import PotelScope as Scope from sentry_sdk.scope import ( - Scope, ScopeType, use_isolation_scope, use_scope, @@ -198,21 +198,18 @@ def test_scope_client(): def test_get_current_scope(): scope = Scope.get_current_scope() assert scope is not None - assert scope.__class__ == Scope assert scope._type == ScopeType.CURRENT def test_get_isolation_scope(): scope = Scope.get_isolation_scope() assert scope is not None - assert scope.__class__ == Scope assert scope._type == ScopeType.ISOLATION def test_get_global_scope(): scope = Scope.get_global_scope() assert scope is not None - assert scope.__class__ == Scope assert scope._type == ScopeType.GLOBAL @@ -223,35 +220,38 @@ def test_get_client(): assert not client.is_active() -def test_set_client(): - client1 = Client() - client2 = Client() - client3 = Client() +def test_set_client(sentry_init): + sentry_init() - current_scope = Scope.get_current_scope() - isolation_scope = Scope.get_isolation_scope() - global_scope = Scope.get_global_scope() + current_client = mock.MagicMock() + isolation_client = mock.MagicMock() + global_client = mock.MagicMock() - current_scope.set_client(client1) - isolation_scope.set_client(client2) - global_scope.set_client(client3) + with sentry_sdk.start_span(): + current_scope = sentry_sdk.get_current_scope() + isolation_scope = sentry_sdk.get_isolation_scope() + global_scope = sentry_sdk.get_global_scope() - client = Scope.get_client() - assert client == client1 + current_scope.set_client(current_client) + isolation_scope.set_client(isolation_client) + global_scope.set_client(global_client) - current_scope.set_client(None) - isolation_scope.set_client(client2) - global_scope.set_client(client3) + client = sentry_sdk.get_client() + assert client == current_client - client = Scope.get_client() - assert client == client2 + current_scope.set_client(None) + isolation_scope.set_client(isolation_client) + global_scope.set_client(global_client) - current_scope.set_client(None) - isolation_scope.set_client(None) - global_scope.set_client(client3) + client = sentry_sdk.get_client() + assert client == isolation_client - client = Scope.get_client() - assert client == client3 + current_scope.set_client(None) + isolation_scope.set_client(None) + global_scope.set_client(global_client) + + client = sentry_sdk.get_client() + assert client == global_client def test_fork():