diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index a44e573d93..7e48423ea4 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -8,6 +8,8 @@ - The `BackgroundWorker` thread used to process events was renamed from `raven-sentry.BackgroundWorker` to `sentry-sdk.BackgroundWorker`. - The `reraise` function was moved from `sentry_sdk._compat` to `sentry_sdk.utils`. +- Moved the contents of `tracing_utils_py3.py` to `tracing_utils.py`. The `start_child_span_decorator` is now in `sentry_sdk.tracing_utils`. +- The actual implementation of `get_current_span` was moved to `sentry_sdk.tracing_utils`. `sentry_sdk.get_current_span` is still accessible as part of the top-level API. ## Removed @@ -17,5 +19,6 @@ - Removed support for Flask 0.\*. - `sentry_sdk._functools` was removed. - A number of compatibility utilities were removed from `sentry_sdk._compat`: the constants `PY2` and `PY33`; the functions `datetime_utcnow`, `utc_from_timestamp`, `implements_str`, `contextmanager`; and the aliases `text_type`, `string_types`, `number_types`, `int_types`, `iteritems`, `binary_sequence_types`. +- Removed `tracing_utils_py2.py`. The `start_child_span_decorator` is now in `sentry_sdk.tracing_utils`. ## Deprecated diff --git a/sentry_sdk/api.py b/sentry_sdk/api.py index f0c6a87432..c71c71f573 100644 --- a/sentry_sdk/api.py +++ b/sentry_sdk/api.py @@ -1,5 +1,6 @@ import inspect +from sentry_sdk import tracing_utils from sentry_sdk._types import TYPE_CHECKING from sentry_sdk.hub import Hub from sentry_sdk.scope import Scope @@ -238,11 +239,7 @@ def get_current_span(hub=None): """ Returns the currently active span if there is one running, otherwise `None` """ - if hub is None: - hub = Hub.current - - current_span = hub.scope.span - return current_span + return tracing_utils.get_current_span(hub) def get_traceparent(): diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index b716a72257..d5c3c99576 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -999,7 +999,7 @@ def my_function(): async def my_async_function(): ... """ - from sentry_sdk.tracing_utils_py3 import start_child_span_decorator + from sentry_sdk.tracing_utils import start_child_span_decorator # This patterns allows usage of both @sentry_traced and @sentry_traced(...) # See https://stackoverflow.com/questions/52126071/decorator-with-arguments-avoid-parenthesis-when-no-arguments/52126278 diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py index dde337f14c..d32007ad05 100644 --- a/sentry_sdk/tracing_utils.py +++ b/sentry_sdk/tracing_utils.py @@ -1,7 +1,9 @@ import contextlib +import inspect import re import sys from collections.abc import Mapping +from functools import wraps from urllib.parse import quote, unquote import sentry_sdk @@ -9,7 +11,9 @@ from sentry_sdk.utils import ( capture_internal_exceptions, Dsn, + logger, match_regex_list, + qualname_from_function, to_string, is_sentry_url, _is_external_source, @@ -501,5 +505,76 @@ def normalize_incoming_data(incoming_data): return data +def start_child_span_decorator(func): + # type: (Any) -> Any + """ + Decorator to add child spans for functions. + + See also ``sentry_sdk.tracing.trace()``. + """ + # Asynchronous case + if inspect.iscoroutinefunction(func): + + @wraps(func) + async def func_with_tracing(*args, **kwargs): + # type: (*Any, **Any) -> Any + + span = get_current_span(sentry_sdk.Hub.current) + + if span is None: + logger.warning( + "Can not create a child span for %s. " + "Please start a Sentry transaction before calling this function.", + qualname_from_function(func), + ) + return await func(*args, **kwargs) + + with span.start_child( + op=OP.FUNCTION, + description=qualname_from_function(func), + ): + return await func(*args, **kwargs) + + # Synchronous case + else: + + @wraps(func) + def func_with_tracing(*args, **kwargs): + # type: (*Any, **Any) -> Any + + span = get_current_span(sentry_sdk.Hub.current) + + if span is None: + logger.warning( + "Can not create a child span for %s. " + "Please start a Sentry transaction before calling this function.", + qualname_from_function(func), + ) + return func(*args, **kwargs) + + with span.start_child( + op=OP.FUNCTION, + description=qualname_from_function(func), + ): + return func(*args, **kwargs) + + return func_with_tracing + + +def get_current_span(hub=None): + # type: (Optional[sentry_sdk.Hub]) -> Optional[Span] + """ + Returns the currently active span if there is one running, otherwise `None` + """ + if hub is None: + hub = sentry_sdk.Hub.current + + current_span = hub.scope.span + return current_span + + # Circular imports from sentry_sdk.tracing import LOW_QUALITY_TRANSACTION_SOURCES + +if TYPE_CHECKING: + from sentry_sdk.tracing import Span diff --git a/sentry_sdk/tracing_utils_py2.py b/sentry_sdk/tracing_utils_py2.py deleted file mode 100644 index a251ab41be..0000000000 --- a/sentry_sdk/tracing_utils_py2.py +++ /dev/null @@ -1,45 +0,0 @@ -from functools import wraps - -import sentry_sdk -from sentry_sdk import get_current_span -from sentry_sdk._types import TYPE_CHECKING -from sentry_sdk.consts import OP -from sentry_sdk.utils import logger, qualname_from_function - - -if TYPE_CHECKING: - from typing import Any - - -def start_child_span_decorator(func): - # type: (Any) -> Any - """ - Decorator to add child spans for functions. - - This is the Python 2 compatible version of the decorator. - Duplicated code from ``sentry_sdk.tracing_utils_python3.start_child_span_decorator``. - - See also ``sentry_sdk.tracing.trace()``. - """ - - @wraps(func) - def func_with_tracing(*args, **kwargs): - # type: (*Any, **Any) -> Any - - span = get_current_span(sentry_sdk.Hub.current) - - if span is None: - logger.warning( - "Can not create a child span for %s. " - "Please start a Sentry transaction before calling this function.", - qualname_from_function(func), - ) - return func(*args, **kwargs) - - with span.start_child( - op=OP.FUNCTION, - description=qualname_from_function(func), - ): - return func(*args, **kwargs) - - return func_with_tracing diff --git a/sentry_sdk/tracing_utils_py3.py b/sentry_sdk/tracing_utils_py3.py deleted file mode 100644 index d58d5f7cb4..0000000000 --- a/sentry_sdk/tracing_utils_py3.py +++ /dev/null @@ -1,72 +0,0 @@ -import inspect -from functools import wraps - -import sentry_sdk -from sentry_sdk import get_current_span -from sentry_sdk._types import TYPE_CHECKING -from sentry_sdk.consts import OP -from sentry_sdk.utils import logger, qualname_from_function - - -if TYPE_CHECKING: - from typing import Any - - -def start_child_span_decorator(func): - # type: (Any) -> Any - """ - Decorator to add child spans for functions. - - This is the Python 3 compatible version of the decorator. - For Python 2 there is duplicated code here: ``sentry_sdk.tracing_utils_python2.start_child_span_decorator()``. - - See also ``sentry_sdk.tracing.trace()``. - """ - - # Asynchronous case - if inspect.iscoroutinefunction(func): - - @wraps(func) - async def func_with_tracing(*args, **kwargs): - # type: (*Any, **Any) -> Any - - span = get_current_span(sentry_sdk.Hub.current) - - if span is None: - logger.warning( - "Can not create a child span for %s. " - "Please start a Sentry transaction before calling this function.", - qualname_from_function(func), - ) - return await func(*args, **kwargs) - - with span.start_child( - op=OP.FUNCTION, - description=qualname_from_function(func), - ): - return await func(*args, **kwargs) - - # Synchronous case - else: - - @wraps(func) - def func_with_tracing(*args, **kwargs): - # type: (*Any, **Any) -> Any - - span = get_current_span(sentry_sdk.Hub.current) - - if span is None: - logger.warning( - "Can not create a child span for %s. " - "Please start a Sentry transaction before calling this function.", - qualname_from_function(func), - ) - return func(*args, **kwargs) - - with span.start_child( - op=OP.FUNCTION, - description=qualname_from_function(func), - ): - return func(*args, **kwargs) - - return func_with_tracing diff --git a/tests/conftest.py b/tests/conftest.py index 6dcda5a5c6..5f82107dc0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -639,7 +639,6 @@ def patch_start_tracing_child(fake_transaction_is_none=False): fake_start_child = None with mock.patch( - "sentry_sdk.tracing_utils_py3.get_current_span", - return_value=fake_transaction, + "sentry_sdk.tracing_utils.get_current_span", return_value=fake_transaction ): yield fake_start_child diff --git a/tests/tracing/test_decorator.py b/tests/tracing/test_decorator.py new file mode 100644 index 0000000000..dba8c24ad3 --- /dev/null +++ b/tests/tracing/test_decorator.py @@ -0,0 +1,76 @@ +from unittest import mock + +import pytest + +from sentry_sdk.tracing_utils import start_child_span_decorator +from sentry_sdk.utils import logger +from tests.conftest import patch_start_tracing_child + + +def my_example_function(): + return "return_of_sync_function" + + +async def my_async_example_function(): + return "return_of_async_function" + + +def test_trace_decorator(): + with patch_start_tracing_child() as fake_start_child: + result = my_example_function() + fake_start_child.assert_not_called() + assert result == "return_of_sync_function" + + result2 = start_child_span_decorator(my_example_function)() + fake_start_child.assert_called_once_with( + op="function", description="test_decorator.my_example_function" + ) + assert result2 == "return_of_sync_function" + + +def test_trace_decorator_no_trx(): + with patch_start_tracing_child(fake_transaction_is_none=True): + with mock.patch.object(logger, "warning", mock.Mock()) as fake_warning: + result = my_example_function() + fake_warning.assert_not_called() + assert result == "return_of_sync_function" + + result2 = start_child_span_decorator(my_example_function)() + fake_warning.assert_called_once_with( + "Can not create a child span for %s. " + "Please start a Sentry transaction before calling this function.", + "test_decorator.my_example_function", + ) + assert result2 == "return_of_sync_function" + + +@pytest.mark.asyncio +async def test_trace_decorator_async(): + with patch_start_tracing_child() as fake_start_child: + result = await my_async_example_function() + fake_start_child.assert_not_called() + assert result == "return_of_async_function" + + result2 = await start_child_span_decorator(my_async_example_function)() + fake_start_child.assert_called_once_with( + op="function", + description="test_decorator.my_async_example_function", + ) + assert result2 == "return_of_async_function" + + +@pytest.mark.asyncio +async def test_trace_decorator_async_no_trx(): + with patch_start_tracing_child(fake_transaction_is_none=True): + with mock.patch.object(logger, "warning", mock.Mock()) as fake_warning: + result = await my_async_example_function() + fake_warning.assert_not_called() + assert result == "return_of_async_function" + + result2 = await start_child_span_decorator(my_async_example_function)() + fake_warning.assert_called_once_with( + "Can not create a child span for %s. " + "Please start a Sentry transaction before calling this function.", + "test_decorator.my_async_example_function", + ) + assert result2 == "return_of_async_function" diff --git a/tests/tracing/test_decorator_async_py3.py b/tests/tracing/test_decorator_async_py3.py deleted file mode 100644 index 401180ad39..0000000000 --- a/tests/tracing/test_decorator_async_py3.py +++ /dev/null @@ -1,49 +0,0 @@ -from unittest import mock -import pytest -import sys - -from tests.conftest import patch_start_tracing_child - -from sentry_sdk.tracing_utils_py3 import ( - start_child_span_decorator as start_child_span_decorator_py3, -) -from sentry_sdk.utils import logger - -if sys.version_info < (3, 6): - pytest.skip("Async decorator only works on Python 3.6+", allow_module_level=True) - - -async def my_async_example_function(): - return "return_of_async_function" - - -@pytest.mark.asyncio -async def test_trace_decorator_async_py3(): - with patch_start_tracing_child() as fake_start_child: - result = await my_async_example_function() - fake_start_child.assert_not_called() - assert result == "return_of_async_function" - - result2 = await start_child_span_decorator_py3(my_async_example_function)() - fake_start_child.assert_called_once_with( - op="function", - description="test_decorator_async_py3.my_async_example_function", - ) - assert result2 == "return_of_async_function" - - -@pytest.mark.asyncio -async def test_trace_decorator_async_py3_no_trx(): - with patch_start_tracing_child(fake_transaction_is_none=True): - with mock.patch.object(logger, "warning", mock.Mock()) as fake_warning: - result = await my_async_example_function() - fake_warning.assert_not_called() - assert result == "return_of_async_function" - - result2 = await start_child_span_decorator_py3(my_async_example_function)() - fake_warning.assert_called_once_with( - "Can not create a child span for %s. " - "Please start a Sentry transaction before calling this function.", - "test_decorator_async_py3.my_async_example_function", - ) - assert result2 == "return_of_async_function" diff --git a/tests/tracing/test_decorator_sync.py b/tests/tracing/test_decorator_sync.py deleted file mode 100644 index 124bc09126..0000000000 --- a/tests/tracing/test_decorator_sync.py +++ /dev/null @@ -1,42 +0,0 @@ -from sentry_sdk.tracing_utils_py3 import start_child_span_decorator -from sentry_sdk.utils import logger - -from tests.conftest import patch_start_tracing_child - -try: - from unittest import mock # python 3.3 and above -except ImportError: - import mock # python < 3.3 - - -def my_example_function(): - return "return_of_sync_function" - - -def test_trace_decorator(): - with patch_start_tracing_child() as fake_start_child: - result = my_example_function() - fake_start_child.assert_not_called() - assert result == "return_of_sync_function" - - result2 = start_child_span_decorator(my_example_function)() - fake_start_child.assert_called_once_with( - op="function", description="test_decorator_sync.my_example_function" - ) - assert result2 == "return_of_sync_function" - - -def test_trace_decorator_no_trx(): - with patch_start_tracing_child(fake_transaction_is_none=True): - with mock.patch.object(logger, "warning", mock.Mock()) as fake_warning: - result = my_example_function() - fake_warning.assert_not_called() - assert result == "return_of_sync_function" - - result2 = start_child_span_decorator(my_example_function)() - fake_warning.assert_called_once_with( - "Can not create a child span for %s. " - "Please start a Sentry transaction before calling this function.", - "test_decorator_sync.my_example_function", - ) - assert result2 == "return_of_sync_function"