diff --git a/docs/api.rst b/docs/api.rst index 87c2535abd..a6fb49346d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -25,6 +25,7 @@ Capturing Data Enriching Events ================ +.. autofunction:: sentry_sdk.api.add_attachment .. autofunction:: sentry_sdk.api.add_breadcrumb .. autofunction:: sentry_sdk.api.set_context .. autofunction:: sentry_sdk.api.set_extra @@ -63,4 +64,3 @@ Managing Scope (advanced) .. autofunction:: sentry_sdk.api.push_scope .. autofunction:: sentry_sdk.api.new_scope - diff --git a/sentry_sdk/__init__.py b/sentry_sdk/__init__.py index b4859cc5d2..9fd7253fc2 100644 --- a/sentry_sdk/__init__.py +++ b/sentry_sdk/__init__.py @@ -15,6 +15,7 @@ "integrations", # From sentry_sdk.api "init", + "add_attachment", "add_breadcrumb", "capture_event", "capture_exception", diff --git a/sentry_sdk/api.py b/sentry_sdk/api.py index a6b3c293dc..e56109cbd0 100644 --- a/sentry_sdk/api.py +++ b/sentry_sdk/api.py @@ -51,6 +51,7 @@ def overload(x): # When changing this, update __all__ in __init__.py too __all__ = [ "init", + "add_attachment", "add_breadcrumb", "capture_event", "capture_exception", @@ -184,6 +185,20 @@ def capture_exception( return get_current_scope().capture_exception(error, scope=scope, **scope_kwargs) +@scopemethod +def add_attachment( + bytes=None, # type: Union[None, bytes, Callable[[], bytes]] + filename=None, # type: Optional[str] + path=None, # type: Optional[str] + content_type=None, # type: Optional[str] + add_to_transactions=False, # type: bool +): + # type: (...) -> None + return get_isolation_scope().add_attachment( + bytes, filename, path, content_type, add_to_transactions + ) + + @scopemethod def add_breadcrumb( crumb=None, # type: Optional[Breadcrumb] diff --git a/sentry_sdk/feature_flags.py b/sentry_sdk/feature_flags.py index dd8d41c32e..eb53acae5d 100644 --- a/sentry_sdk/feature_flags.py +++ b/sentry_sdk/feature_flags.py @@ -64,7 +64,7 @@ def add_feature_flag(flag, result): Records a flag and its value to be sent on subsequent error events. We recommend you do this on flag evaluations. Flags are buffered per Sentry scope. """ - flags = sentry_sdk.get_current_scope().flags + flags = sentry_sdk.get_isolation_scope().flags flags.set(flag, result) span = sentry_sdk.get_current_span() diff --git a/tests/integrations/fastapi/test_fastapi.py b/tests/integrations/fastapi/test_fastapi.py index 95838b1009..3d79da92cc 100644 --- a/tests/integrations/fastapi/test_fastapi.py +++ b/tests/integrations/fastapi/test_fastapi.py @@ -10,7 +10,9 @@ from fastapi.testclient import TestClient from fastapi.middleware.trustedhost import TrustedHostMiddleware +import sentry_sdk from sentry_sdk import capture_message +from sentry_sdk.feature_flags import add_feature_flag from sentry_sdk.integrations.asgi import SentryAsgiMiddleware from sentry_sdk.integrations.fastapi import FastApiIntegration from sentry_sdk.integrations.starlette import StarletteIntegration @@ -714,3 +716,41 @@ async def subapp_route(): assert event["transaction"] == "/subapp" else: assert event["transaction"].endswith("subapp_route") + + +@pytest.mark.asyncio +async def test_feature_flags(sentry_init, capture_events): + sentry_init( + traces_sample_rate=1.0, + integrations=[StarletteIntegration(), FastApiIntegration()], + ) + + events = capture_events() + + app = FastAPI() + + @app.get("/error") + async def _error(): + add_feature_flag("hello", False) + + with sentry_sdk.start_span(name="test-span"): + with sentry_sdk.start_span(name="test-span-2"): + raise ValueError("something is wrong!") + + try: + client = TestClient(app) + client.get("/error") + except ValueError: + pass + + found = False + for event in events: + if "exception" in event.keys(): + assert event["contexts"]["flags"] == { + "values": [ + {"flag": "hello", "result": False}, + ] + } + found = True + + assert found, "No event with exception found" diff --git a/tests/test_feature_flags.py b/tests/test_feature_flags.py index 1b0ed13d49..e0ab1e254e 100644 --- a/tests/test_feature_flags.py +++ b/tests/test_feature_flags.py @@ -31,6 +31,63 @@ def test_featureflags_integration(sentry_init, capture_events, uninstall_integra } +@pytest.mark.asyncio +async def test_featureflags_integration_spans_async(sentry_init, capture_events): + sentry_init( + traces_sample_rate=1.0, + ) + events = capture_events() + + add_feature_flag("hello", False) + + try: + with sentry_sdk.start_span(name="test-span"): + with sentry_sdk.start_span(name="test-span-2"): + raise ValueError("something wrong!") + except ValueError as e: + sentry_sdk.capture_exception(e) + + found = False + for event in events: + if "exception" in event.keys(): + assert event["contexts"]["flags"] == { + "values": [ + {"flag": "hello", "result": False}, + ] + } + found = True + + assert found, "No event with exception found" + + +def test_featureflags_integration_spans_sync(sentry_init, capture_events): + sentry_init( + traces_sample_rate=1.0, + ) + events = capture_events() + + add_feature_flag("hello", False) + + try: + with sentry_sdk.start_span(name="test-span"): + with sentry_sdk.start_span(name="test-span-2"): + raise ValueError("something wrong!") + except ValueError as e: + sentry_sdk.capture_exception(e) + + found = False + for event in events: + if "exception" in event.keys(): + assert event["contexts"]["flags"] == { + "values": [ + {"flag": "hello", "result": False}, + ] + } + found = True + + assert found, "No event with exception found" + + def test_featureflags_integration_threaded( sentry_init, capture_events, uninstall_integration ):