diff --git a/sentry_sdk/integrations/fastapi.py b/sentry_sdk/integrations/fastapi.py index 0b048e11ee..dc408797e3 100644 --- a/sentry_sdk/integrations/fastapi.py +++ b/sentry_sdk/integrations/fastapi.py @@ -9,7 +9,7 @@ from sentry_sdk.traces import StreamedSpan, get_current_span from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource from sentry_sdk.tracing_utils import has_span_streaming_enabled -from sentry_sdk.utils import transaction_from_function +from sentry_sdk.utils import has_data_collection_enabled, transaction_from_function if TYPE_CHECKING: from typing import Any, Awaitable, Callable, Dict @@ -120,7 +120,15 @@ def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event": if "cookies" in info: request_info["cookies"] = info["cookies"] if "data" in info: - request_info["data"] = info["data"] + attach_request_data = True + if has_data_collection_enabled(client.options): + attach_request_data = ( + "incoming_request" + in client.options["data_collection"]["http_bodies"] + ) + + if attach_request_data: + request_info["data"] = info["data"] event["request"] = deepcopy(request_info) return event @@ -138,14 +146,22 @@ def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event": current_span = get_current_span() if type(current_span) is StreamedSpan: - request_body = _get_cached_request_body_attribute( - client=client, request=request - ) - if request_body: - current_span._segment.set_attribute( - SPANDATA.HTTP_REQUEST_BODY_DATA, - request_body, + attach_request_data = True + if has_data_collection_enabled(client.options): + attach_request_data = ( + "incoming_request" + in client.options["data_collection"]["http_bodies"] + ) + + if attach_request_data: + request_body = _get_cached_request_body_attribute( + client=client, request=request ) + if request_body: + current_span._segment.set_attribute( + SPANDATA.HTTP_REQUEST_BODY_DATA, + request_body, + ) def patch_get_request_handler() -> None: diff --git a/tests/integrations/fastapi/test_fastapi.py b/tests/integrations/fastapi/test_fastapi.py index 913396f356..4a1df70d7c 100644 --- a/tests/integrations/fastapi/test_fastapi.py +++ b/tests/integrations/fastapi/test_fastapi.py @@ -348,6 +348,119 @@ async def test_request_body_too_big( } +@pytest.mark.asyncio +@pytest.mark.parametrize("span_streaming", [True, False]) +async def test_formdata_request_body_data_collection_http_bodies_empty( + sentry_init, capture_events, capture_items, span_streaming +): + sentry_init( + traces_sample_rate=1.0, + max_request_body_size="always", + integrations=[StarletteIntegration()], + trace_lifecycle="stream" if span_streaming else "static", + _experiments={"data_collection": {"http_bodies": []}}, + ) + + app = fastapi_app_factory() + client = TestClient(app) + + headers = {"content-type": "multipart/form-data; boundary=fd721ef49ea403a6"} + + if span_streaming: + items = capture_items("event", "span") + + client.post("/body/form", data=BODY_FORM.encode("utf-8"), headers=headers) + + (event,) = (item.payload for item in items if item.type == "event") + assert "data" not in event["request"] + + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + server_span = next( + span for span in spans if span["attributes"]["sentry.op"] == "http.server" + ) + assert SPANDATA.HTTP_REQUEST_BODY_DATA not in server_span["attributes"] + else: + events = capture_events() + + client.post("/body/form", data=BODY_FORM.encode("utf-8"), headers=headers) + + (event, _) = events + assert "data" not in event["request"] + + +@pytest.mark.asyncio +@pytest.mark.parametrize("span_streaming", [True, False]) +@pytest.mark.parametrize( + "data_collection, expect_body", + [ + pytest.param(None, True, id="no_data_collection_experiment"), + pytest.param({}, True, id="data_collection_http_bodies_default"), + pytest.param( + {"http_bodies": ["incoming_request"]}, + True, + id="data_collection_http_bodies_incoming_request", + ), + pytest.param( + {"http_bodies": []}, False, id="data_collection_http_bodies_empty" + ), + ], +) +async def test_request_body_data_collection( + sentry_init, + capture_events, + capture_items, + span_streaming, + data_collection, + expect_body, +): + sentry_init( + traces_sample_rate=1.0, + integrations=[StarletteIntegration()], + trace_lifecycle="stream" if span_streaming else "static", + _experiments=( + {} if data_collection is None else {"data_collection": data_collection} + ), + ) + + app = fastapi_app_factory() + client = TestClient(app) + + if span_streaming: + items = capture_items("event", "span") + + client.post("/body/json", json=BODY_JSON) + + (event,) = (item.payload for item in items if item.type == "event") + + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + server_span = next( + span for span in spans if span["attributes"]["sentry.op"] == "http.server" + ) + + if expect_body: + assert event["request"]["data"] == BODY_JSON + assert ( + json.loads(server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA]) + == BODY_JSON + ) + else: + assert "data" not in event["request"] + assert SPANDATA.HTTP_REQUEST_BODY_DATA not in server_span["attributes"] + else: + events = capture_events() + + client.post("/body/json", json=BODY_JSON) + + (event, _) = events + + if expect_body: + assert event["request"]["data"] == BODY_JSON + else: + assert "data" not in event["request"] + + @pytest.mark.asyncio async def test_response(sentry_init, capture_events): # FastAPI is heavily based on Starlette so we also need