From f4f08c94b6ebc0e256be817720ede6d0e0281fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 21 Feb 2022 16:57:55 +0100 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20setting?= =?UTF-8?q?=20transaction=20name=20to=20path=20in=20FastAPI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sentry_sdk/integrations/asgi.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index 29812fce7c..0f3ae2f816 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -37,6 +37,8 @@ _DEFAULT_TRANSACTION_NAME = "generic ASGI request" +TRANSACTION_STYLE_VALUES = ("endpoint", "url") + def _capture_exception(hub, exc): # type: (Hub, Any) -> None @@ -68,10 +70,10 @@ def _looks_like_asgi3(app): class SentryAsgiMiddleware: - __slots__ = ("app", "__call__") + __slots__ = ("app", "__call__", "transaction_style") - def __init__(self, app, unsafe_context_data=False): - # type: (Any, bool) -> None + def __init__(self, app, unsafe_context_data=False, transaction_style="endpoint"): + # type: (Any, bool, str) -> None """ Instrument an ASGI application with Sentry. Provides HTTP/websocket data to sent events and basic handling for exceptions bubbling up @@ -87,6 +89,12 @@ def __init__(self, app, unsafe_context_data=False): "The ASGI middleware for Sentry requires Python 3.7+ " "or the aiocontextvars package." + CONTEXTVARS_ERROR_MESSAGE ) + if transaction_style not in TRANSACTION_STYLE_VALUES: + raise ValueError( + "Invalid value for transaction_style: %s (must be in %s)" + % (transaction_style, TRANSACTION_STYLE_VALUES) + ) + self.transaction_style = transaction_style self.app = app if _looks_like_asgi3(app): @@ -185,6 +193,14 @@ def event_processor(self, event, hint, asgi_scope): # an endpoint, overwrite our generic transaction name. if endpoint: event["transaction"] = transaction_from_function(endpoint) + # FastAPI includes the route object in the scope to let Sentry extract the + # path from it for the transaction name + if self.transaction_style == "url": + route = asgi_scope.get("route") + if route: + path = getattr(route, "path", None) + if path is not None: + event["transaction"] = path event["request"] = request_info From cb363d7588f4a9f5c0968cdb710a2e5f60b06172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 21 Feb 2022 16:59:38 +0100 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9C=85=20Add=20test=20for=20FastAPI/Sent?= =?UTF-8?q?ryAsgiMiddleware=20with=20transaction=5Fstyle=3D"url"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/integrations/asgi/test_fastapi.py | 46 +++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/integrations/asgi/test_fastapi.py diff --git a/tests/integrations/asgi/test_fastapi.py b/tests/integrations/asgi/test_fastapi.py new file mode 100644 index 0000000000..518b8544b2 --- /dev/null +++ b/tests/integrations/asgi/test_fastapi.py @@ -0,0 +1,46 @@ +import sys + +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient +from sentry_sdk import capture_message +from sentry_sdk.integrations.asgi import SentryAsgiMiddleware + + +@pytest.fixture +def app(): + app = FastAPI() + + @app.get("/users/{user_id}") + async def get_user(user_id: str): + capture_message("hi", level="error") + return {"user_id": user_id} + + app.add_middleware(SentryAsgiMiddleware, transaction_style="url") + + return app + + +@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher") +def test_fastapi_transaction_style(sentry_init, app, capture_events): + sentry_init(send_default_pii=True) + events = capture_events() + + client = TestClient(app) + response = client.get("/users/rick") + + assert response.status_code == 200 + + (event,) = events + assert event["transaction"] == "/users/{user_id}" + assert event["request"]["env"] == {"REMOTE_ADDR": "testclient"} + assert event["request"]["url"].endswith("/users/rick") + assert event["request"]["method"] == "GET" + + # Assert that state is not leaked + events.clear() + capture_message("foo") + (event,) = events + + assert "request" not in event + assert "transaction" not in event From 9df5e7d941fd3ccbf6f4780b1a590db675c5d0c0 Mon Sep 17 00:00:00 2001 From: Neel Shah Date: Thu, 17 Mar 2022 14:45:43 +0100 Subject: [PATCH 3/3] Add fastapi to tox, add if for transaction_style --- sentry_sdk/integrations/asgi.py | 19 ++++++++++--------- tox.ini | 1 + 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index 0f3ae2f816..5f7810732b 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -187,15 +187,16 @@ def event_processor(self, event, hint, asgi_scope): event.get("transaction", _DEFAULT_TRANSACTION_NAME) == _DEFAULT_TRANSACTION_NAME ): - endpoint = asgi_scope.get("endpoint") - # Webframeworks like Starlette mutate the ASGI env once routing is - # done, which is sometime after the request has started. If we have - # an endpoint, overwrite our generic transaction name. - if endpoint: - event["transaction"] = transaction_from_function(endpoint) - # FastAPI includes the route object in the scope to let Sentry extract the - # path from it for the transaction name - if self.transaction_style == "url": + if self.transaction_style == "endpoint": + endpoint = asgi_scope.get("endpoint") + # Webframeworks like Starlette mutate the ASGI env once routing is + # done, which is sometime after the request has started. If we have + # an endpoint, overwrite our generic transaction name. + if endpoint: + event["transaction"] = transaction_from_function(endpoint) + elif self.transaction_style == "url": + # FastAPI includes the route object in the scope to let Sentry extract the + # path from it for the transaction name route = asgi_scope.get("route") if route: path = getattr(route, "path", None) diff --git a/tox.ini b/tox.ini index cb158d7209..bc087ad23c 100644 --- a/tox.ini +++ b/tox.ini @@ -212,6 +212,7 @@ deps = asgi: starlette asgi: requests + asgi: fastapi sqlalchemy-1.2: sqlalchemy>=1.2,<1.3 sqlalchemy-1.3: sqlalchemy>=1.3,<1.4