diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index 4f7b421357..1ae4d47eb2 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -46,6 +46,7 @@ AnnotatedValue, _get_aws_sigv4_signed_headers_from_authorization_header, _get_aws_sigv4_signed_headers_from_url_query_string, + _is_localhost, _register_control_flow_exception, capture_internal_exceptions, ensure_integration_enabled, @@ -146,6 +147,17 @@ async def sentry_app_handle( scope.clear_breadcrumbs() scope.add_event_processor(_make_request_processor(weak_request)) + scope.set_attribute( + SPANDATA.SENTRY_IS_LOCALHOST, + _is_localhost( + client_ip=_get_client_ip(request), + host_header=request.headers.get("Host"), + forwarded_host_header=request.headers.get( + "X-Forwarded-Host" + ), + ), + ) + headers = dict(request.headers) span_ctx: "ContextManager[Union[Span, StreamedSpan]]" @@ -643,6 +655,17 @@ def aiohttp_processor( return aiohttp_processor +def _get_client_ip(request: "Request") -> "Optional[str]": + x_forwarded_for = request.headers.get("X-Forwarded-For", "").split(",")[0].strip() + if x_forwarded_for: + return x_forwarded_for + + if request.headers.get("X-Real-IP"): + return request.headers["X-Real-IP"] + + return request.remote + + def _capture_exception() -> "ExcInfo": exc_info = sys.exc_info() event, hint = event_from_exception( diff --git a/tests/integrations/aiohttp/test_aiohttp.py b/tests/integrations/aiohttp/test_aiohttp.py index 1c3166de64..b4301ad628 100644 --- a/tests/integrations/aiohttp/test_aiohttp.py +++ b/tests/integrations/aiohttp/test_aiohttp.py @@ -2416,6 +2416,62 @@ async def hello(request): assert event["request"]["query_string"] == expected_query +@pytest.mark.asyncio +@pytest.mark.parametrize( + "forwarded_for, host_header, is_localhost", + [ + pytest.param(None, None, True, id="loopback-ip"), + pytest.param( + "93.184.216.34", "localhost:8080", True, id="localhost-host-header" + ), + pytest.param("93.184.216.34", "example.com", False, id="non-local"), + ], +) +async def test_is_localhost_attribute( + sentry_init, + aiohttp_client, + capture_items, + forwarded_for, + host_header, + is_localhost, +): + sentry_init( + integrations=[AioHttpIntegration()], + traces_sample_rate=1.0, + trace_lifecycle="stream", + ) + + async def hello(request): + with sentry_sdk.traces.start_span(name="child"): + pass + return web.Response(text="hello") + + app = web.Application() + app.router.add_get("/", hello) + + items = capture_items("span") + + client = await aiohttp_client(app) + headers = {} + if host_header: + headers["Host"] = host_header + if forwarded_for: + headers["X-Forwarded-For"] = forwarded_for + resp = await client.get("/", headers=headers) + assert resp.status == 200 + + sentry_sdk.flush() + + child_spans = [item.payload for item in items if not item.payload.get("is_segment")] + server_spans = [item.payload for item in items if item.payload.get("is_segment")] + + assert len(server_spans) == 1 + assert len(child_spans) == 1 + + assert server_spans[0]["attributes"][SPANDATA.SENTRY_IS_LOCALHOST] == is_localhost + assert child_spans[0]["attributes"][SPANDATA.SENTRY_IS_LOCALHOST] == is_localhost + + @pytest.mark.asyncio @pytest.mark.parametrize( "init_kwargs, expect_remote_addr", DATA_COLLECTION_REMOTE_ADDR_CASES