diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index 776d9eef0e..2b65608fe7 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -566,7 +566,14 @@ def aiohttp_processor( request_info["query_string"] = request.query_string request_info["method"] = request.method - request_info["env"] = {"REMOTE_ADDR": request.remote} + + # REMOTE_ADDR was unconditionally set pre-data collection, so it + # continues to be set when data collection is not enabled. + if ( + not has_data_collection_enabled(client_options) + or client_options["data_collection"]["user_info"] + ): + request_info["env"] = {"REMOTE_ADDR": request.remote} request_info["headers"] = _filter_headers(dict(request.headers)) # Just attach raw data here if it is within bounds, if available. diff --git a/sentry_sdk/integrations/sanic.py b/sentry_sdk/integrations/sanic.py index 2d839d1c61..2545f8f780 100644 --- a/sentry_sdk/integrations/sanic.py +++ b/sentry_sdk/integrations/sanic.py @@ -463,7 +463,14 @@ def sanic_processor(event: "Event", hint: "Optional[Hint]") -> "Optional[Event]" request_info["query_string"] = urlparts.query request_info["method"] = request.method - request_info["env"] = {"REMOTE_ADDR": request.remote_addr} + + # REMOTE_ADDR was unconditionally set pre-data collection, so it + # continues to be set when data collection is not enabled. + if ( + not has_data_collection_enabled(client_options) + or client_options["data_collection"]["user_info"] + ): + request_info["env"] = {"REMOTE_ADDR": request.remote_addr} request_info["headers"] = _filter_headers(dict(request.headers)) return event diff --git a/sentry_sdk/integrations/tornado.py b/sentry_sdk/integrations/tornado.py index 3ae20ef83f..d0be7fceb0 100644 --- a/sentry_sdk/integrations/tornado.py +++ b/sentry_sdk/integrations/tornado.py @@ -330,7 +330,14 @@ def tornado_processor(event: "Event", hint: "dict[str, Any]") -> "Event": request_info["query_string"] = request.query request_info["method"] = request.method - request_info["env"] = {"REMOTE_ADDR": request.remote_ip} + + # REMOTE_ADDR was unconditionally set pre-data collection, so it + # continues to be set when data collection is not enabled. + if ( + not has_data_collection_enabled(client_options) + or client_options["data_collection"]["user_info"] + ): + request_info["env"] = {"REMOTE_ADDR": request.remote_ip} request_info["headers"] = _filter_headers(dict(request.headers)) if has_data_collection_enabled(client_options): diff --git a/tests/integrations/aiohttp/test_aiohttp.py b/tests/integrations/aiohttp/test_aiohttp.py index 04e7312b80..529635baf5 100644 --- a/tests/integrations/aiohttp/test_aiohttp.py +++ b/tests/integrations/aiohttp/test_aiohttp.py @@ -27,7 +27,10 @@ ) from sentry_sdk.utils import SENSITIVE_DATA_SUBSTITUTE from tests.conftest import ApproxDict -from tests.integrations.utils import DATA_COLLECTION_USER_INFO_CASES +from tests.integrations.utils import ( + DATA_COLLECTION_REMOTE_ADDR_CASES, + DATA_COLLECTION_USER_INFO_CASES, +) @pytest.mark.asyncio @@ -2268,3 +2271,32 @@ async def hello(request): assert "url.query" not in inner_client_span["attributes"] else: assert inner_client_span["attributes"]["url.query"] == expected_query + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "init_kwargs, expect_remote_addr", DATA_COLLECTION_REMOTE_ADDR_CASES +) +async def test_remote_addr_data_collection( + sentry_init, aiohttp_client, capture_events, init_kwargs, expect_remote_addr +): + sentry_init(integrations=[AioHttpIntegration()], **init_kwargs) + + async def hello(request): + capture_message("hi") + return web.Response(text="hello") + + app = web.Application() + app.router.add_get("/", hello) + + events = capture_events() + + client = await aiohttp_client(app) + resp = await client.get("/") + assert resp.status == 200 + + (event,) = events + if expect_remote_addr: + assert event["request"]["env"] == {"REMOTE_ADDR": "127.0.0.1"} + else: + assert "env" not in event["request"] diff --git a/tests/integrations/sanic/test_sanic.py b/tests/integrations/sanic/test_sanic.py index 03e68a82e1..0c84f63044 100644 --- a/tests/integrations/sanic/test_sanic.py +++ b/tests/integrations/sanic/test_sanic.py @@ -16,7 +16,10 @@ from sentry_sdk.integrations.sanic import SanicIntegration from sentry_sdk.tracing import TransactionSource from tests.conftest import get_free_port -from tests.integrations.utils import DATA_COLLECTION_USER_INFO_CASES +from tests.integrations.utils import ( + DATA_COLLECTION_REMOTE_ADDR_CASES, + DATA_COLLECTION_USER_INFO_CASES, +) try: from sanic_testing import TestManager @@ -874,3 +877,24 @@ def oversized_handler(request): assert "data" not in event["request"] assert "data" not in event.get("_meta", {}).get("request", {}) + + +@pytest.mark.parametrize( + "init_kwargs, expect_remote_addr", DATA_COLLECTION_REMOTE_ADDR_CASES +) +def test_remote_addr_data_collection( + sentry_init, app, capture_events, init_kwargs, expect_remote_addr +): + sentry_init(integrations=[SanicIntegration()], **init_kwargs) + events = capture_events() + + c = get_client(app) + with c as client: + _, response = client.get("/message") + assert response.status == 200 + + (event,) = events + if expect_remote_addr: + assert event["request"]["env"] == {"REMOTE_ADDR": ""} + else: + assert "env" not in event["request"] diff --git a/tests/integrations/tornado/test_tornado.py b/tests/integrations/tornado/test_tornado.py index b8582ae964..063e14b1e5 100644 --- a/tests/integrations/tornado/test_tornado.py +++ b/tests/integrations/tornado/test_tornado.py @@ -8,7 +8,10 @@ from sentry_sdk import capture_message, start_transaction from sentry_sdk._types import SENSITIVE_DATA_SUBSTITUTE from sentry_sdk.integrations.tornado import TornadoIntegration -from tests.integrations.utils import DATA_COLLECTION_USER_INFO_CASES +from tests.integrations.utils import ( + DATA_COLLECTION_REMOTE_ADDR_CASES, + DATA_COLLECTION_USER_INFO_CASES, +) @pytest.fixture @@ -1133,3 +1136,23 @@ def test_client_address_span_attribute_data_collection( assert server_span["attributes"]["client.address"] == "127.0.0.1" else: assert "client.address" not in server_span["attributes"] + + +@pytest.mark.parametrize( + "init_kwargs, expect_remote_addr", DATA_COLLECTION_REMOTE_ADDR_CASES +) +def test_remote_addr_data_collection( + tornado_testcase, sentry_init, capture_events, init_kwargs, expect_remote_addr +): + sentry_init(integrations=[TornadoIntegration()], **init_kwargs) + events = capture_events() + client = tornado_testcase(Application([(r"/hi", CrashingHandler)])) + + response = client.fetch("/hi") + assert response.code == 500 + + (event,) = events + if expect_remote_addr: + assert event["request"]["env"] == {"REMOTE_ADDR": "127.0.0.1"} + else: + assert "env" not in event["request"] diff --git a/tests/integrations/utils.py b/tests/integrations/utils.py index bb841cfa6e..8980c4f80c 100644 --- a/tests/integrations/utils.py +++ b/tests/integrations/utils.py @@ -39,6 +39,38 @@ ), ] +# Shared parametrization test matrix for ``REMOTE_ADDR`` on events in +# integrations that set it unconditionally pre-data collection (tornado, sanic, +# aiohttp). Each case is ``(init_kwargs, expect_remote_addr)``: the address is +# only gated once ``data_collection`` is enabled, so the legacy +# ``send_default_pii`` cases still expect it to be collected. +DATA_COLLECTION_REMOTE_ADDR_CASES = [ + pytest.param({}, True, id="defaults"), + pytest.param({"send_default_pii": True}, True, id="send_default_pii_true"), + pytest.param({"send_default_pii": False}, True, id="send_default_pii_false"), + pytest.param( + {"_experiments": {"data_collection": {}}}, True, id="data_collection_default" + ), + pytest.param( + {"_experiments": {"data_collection": {"user_info": True}}}, + True, + id="data_collection_user_info_true", + ), + pytest.param( + {"_experiments": {"data_collection": {"user_info": False}}}, + False, + id="data_collection_user_info_false", + ), + pytest.param( + { + "send_default_pii": True, + "_experiments": {"data_collection": {"user_info": False}}, + }, + False, + id="data_collection_wins_over_send_default_pii", + ), +] + # Shared parametrization test matrix exercising the interaction between the # ``data_collection.queues`` experiment and the legacy ``send_default_pii`` boolean # for job/task args and kwargs collected by queue integrations (rq, arq, huey).