Skip to content

Commit e2e237f

Browse files
sentrivanaclaude
andcommitted
test: Add tests for user.ip_address on all spans
Add test_user_ip_address_on_all_spans to wsgi, asgi, aiohttp, tornado, and sanic. Each test creates a child span and verifies user.ip_address is present on both server and child spans when send_default_pii=True. Fix tornado and sanic to set user.ip_address on the isolation scope before the handler runs, so child spans inherit the attribute. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 81c12f4 commit e2e237f

7 files changed

Lines changed: 214 additions & 6 deletions

File tree

sentry_sdk/integrations/sanic.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,11 @@ async def _context_enter(request: "Request") -> None:
181181
sentry_sdk.traces.continue_trace(dict(request.headers))
182182
scope.set_custom_sampling_context({"sanic_request": request})
183183

184+
if should_send_default_pii() and request.remote_addr:
185+
sentry_sdk.get_isolation_scope().set_attribute(
186+
SPANDATA.USER_IP_ADDRESS, request.remote_addr
187+
)
188+
184189
span = sentry_sdk.traces.start_span(
185190
# Unless the request results in a 404 error, the name and source
186191
# will get overwritten in _set_transaction
@@ -375,9 +380,6 @@ def _get_request_attributes(request: "Request") -> "Dict[str, Any]":
375380

376381
if should_send_default_pii() and request.remote_addr:
377382
attributes[SPANDATA.CLIENT_ADDRESS] = request.remote_addr
378-
sentry_sdk.get_isolation_scope().set_attribute(
379-
SPANDATA.USER_IP_ADDRESS, request.remote_addr
380-
)
381383

382384
return attributes
383385

sentry_sdk/integrations/tornado.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ def _handle_request_impl(self: "RequestHandler") -> "Generator[None, None, None]
130130
sentry_sdk.traces.continue_trace(dict(headers))
131131
scope.set_custom_sampling_context({"tornado_request": self.request})
132132

133+
if should_send_default_pii() and self.request.remote_ip:
134+
sentry_sdk.get_isolation_scope().set_attribute(
135+
SPANDATA.USER_IP_ADDRESS, self.request.remote_ip
136+
)
137+
133138
span_ctx = sentry_sdk.traces.start_span(
134139
name=_DEFAULT_ROOT_SPAN_NAME,
135140
attributes={
@@ -204,9 +209,6 @@ def _get_request_attributes(request: "Any") -> "Dict[str, Any]":
204209

205210
if should_send_default_pii() and request.remote_ip:
206211
attributes[SPANDATA.CLIENT_ADDRESS] = request.remote_ip
207-
sentry_sdk.get_isolation_scope().set_attribute(
208-
SPANDATA.USER_IP_ADDRESS, request.remote_ip
209-
)
210212

211213
with capture_internal_exceptions():
212214
raw_data = _get_tornado_request_data(request)

tests/integrations/aiohttp/test_aiohttp.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1590,3 +1590,40 @@ async def handler(request):
15901590
span_id=client_span["span_id"],
15911591
sampled=1,
15921592
)
1593+
1594+
1595+
@pytest.mark.asyncio
1596+
@pytest.mark.parametrize("send_default_pii", [True, False])
1597+
async def test_user_ip_address_on_all_spans(
1598+
sentry_init, aiohttp_client, capture_items, send_default_pii
1599+
):
1600+
sentry_init(
1601+
integrations=[AioHttpIntegration()],
1602+
traces_sample_rate=1.0,
1603+
send_default_pii=send_default_pii,
1604+
_experiments={"trace_lifecycle": "stream"},
1605+
)
1606+
1607+
async def hello(request):
1608+
with sentry_sdk.traces.start_span(name="child-span"):
1609+
pass
1610+
return web.Response(text="hello")
1611+
1612+
app = web.Application()
1613+
app.router.add_get("/", hello)
1614+
1615+
items = capture_items("span")
1616+
1617+
client = await aiohttp_client(app)
1618+
await client.get("/")
1619+
1620+
sentry_sdk.flush()
1621+
1622+
child_span, server_span, client_span = [item.payload for item in items]
1623+
1624+
if send_default_pii:
1625+
assert server_span["attributes"]["user.ip_address"] == "127.0.0.1"
1626+
assert child_span["attributes"]["user.ip_address"] == "127.0.0.1"
1627+
else:
1628+
assert "user.ip_address" not in server_span["attributes"]
1629+
assert "user.ip_address" not in child_span["attributes"]

tests/integrations/asgi/test_asgi.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,3 +1002,59 @@ async def test_custom_transaction_name(
10021002
assert transaction_event["type"] == "transaction"
10031003
assert transaction_event["transaction"] == "foobar"
10041004
assert transaction_event["transaction_info"] == {"source": "custom"}
1005+
1006+
1007+
@pytest.mark.asyncio
1008+
@pytest.mark.parametrize("send_default_pii", [True, False])
1009+
async def test_user_ip_address_on_all_spans(
1010+
sentry_init,
1011+
capture_items,
1012+
send_default_pii,
1013+
):
1014+
async def app(scope, receive, send):
1015+
if scope["type"] == "lifespan":
1016+
while True:
1017+
message = await receive()
1018+
if message["type"] == "lifespan.startup":
1019+
await send({"type": "lifespan.startup.complete"})
1020+
elif message["type"] == "lifespan.shutdown":
1021+
await send({"type": "lifespan.shutdown.complete"})
1022+
return
1023+
1024+
with sentry_sdk.traces.start_span(name="child-span"):
1025+
pass
1026+
1027+
await send(
1028+
{
1029+
"type": "http.response.start",
1030+
"status": 200,
1031+
"headers": [[b"content-type", b"text/plain"]],
1032+
}
1033+
)
1034+
await send({"type": "http.response.body", "body": b"Hello, world!"})
1035+
1036+
sentry_init(
1037+
send_default_pii=send_default_pii,
1038+
traces_sample_rate=1.0,
1039+
_experiments={"trace_lifecycle": "stream"},
1040+
)
1041+
sentry_app = SentryAsgiMiddleware(app)
1042+
1043+
async def wrapped_app(scope, receive, send):
1044+
scope["client"] = ("127.0.0.1", 0)
1045+
await sentry_app(scope, receive, send)
1046+
1047+
async with TestClient(wrapped_app) as client:
1048+
items = capture_items("span")
1049+
await client.get("/some_url")
1050+
1051+
sentry_sdk.flush()
1052+
1053+
child_span, server_span = [item.payload for item in items]
1054+
1055+
if send_default_pii:
1056+
assert server_span["attributes"]["user.ip_address"] == "127.0.0.1"
1057+
assert child_span["attributes"]["user.ip_address"] == "127.0.0.1"
1058+
else:
1059+
assert "user.ip_address" not in server_span["attributes"]
1060+
assert "user.ip_address" not in child_span["attributes"]

tests/integrations/sanic/test_sanic.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,3 +541,47 @@ def test_span_origin(sentry_init, app, capture_events, capture_items, span_strea
541541
else:
542542
(_, event) = events
543543
assert event["contexts"]["trace"]["origin"] == "auto.http.sanic"
544+
545+
546+
@pytest.mark.skipif(
547+
not PERFORMANCE_SUPPORTED, reason="Performance not supported on this Sanic version"
548+
)
549+
@pytest.mark.parametrize("send_default_pii", [True, False])
550+
def test_user_ip_address_on_all_spans(
551+
sentry_init, app, capture_items, send_default_pii
552+
):
553+
app.config.FORWARDED_SECRET = "test"
554+
555+
@app.route("/child-span")
556+
def child_span_handler(request):
557+
with sentry_sdk.traces.start_span(name="child-span"):
558+
pass
559+
return response.text("ok")
560+
561+
sentry_init(
562+
integrations=[SanicIntegration()],
563+
default_integrations=False,
564+
traces_sample_rate=1.0,
565+
send_default_pii=send_default_pii,
566+
_experiments={"trace_lifecycle": "stream"},
567+
)
568+
569+
items = capture_items("span")
570+
571+
c = get_client(app)
572+
with c as client:
573+
client.get(
574+
"/child-span",
575+
headers={"Forwarded": "for=127.0.0.1;secret=test"},
576+
)
577+
578+
sentry_sdk.flush()
579+
580+
child_span, server_span = [item.payload for item in items]
581+
582+
if send_default_pii:
583+
assert server_span["attributes"]["user.ip_address"] == "127.0.0.1"
584+
assert child_span["attributes"]["user.ip_address"] == "127.0.0.1"
585+
else:
586+
assert "user.ip_address" not in server_span["attributes"]
587+
assert "user.ip_address" not in child_span["attributes"]

tests/integrations/tornado/test_tornado.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,3 +527,38 @@ def test_span_origin(
527527
else:
528528
(_, event) = events
529529
assert event["contexts"]["trace"]["origin"] == "auto.http.tornado"
530+
531+
532+
class ChildSpanHandler(RequestHandler):
533+
def get(self):
534+
with sentry_sdk.traces.start_span(name="child-span"):
535+
pass
536+
self.write("ok")
537+
538+
539+
@pytest.mark.parametrize("send_default_pii", [True, False])
540+
def test_user_ip_address_on_all_spans(
541+
tornado_testcase, sentry_init, capture_items, send_default_pii
542+
):
543+
sentry_init(
544+
integrations=[TornadoIntegration()],
545+
traces_sample_rate=1.0,
546+
send_default_pii=send_default_pii,
547+
_experiments={"trace_lifecycle": "stream"},
548+
)
549+
550+
items = capture_items("span")
551+
552+
client = tornado_testcase(Application([(r"/hi", ChildSpanHandler)]))
553+
client.fetch("/hi")
554+
555+
sentry_sdk.flush()
556+
557+
child_span, server_span = [item.payload for item in items]
558+
559+
if send_default_pii:
560+
assert server_span["attributes"]["user.ip_address"] == "127.0.0.1"
561+
assert child_span["attributes"]["user.ip_address"] == "127.0.0.1"
562+
else:
563+
assert "user.ip_address" not in server_span["attributes"]
564+
assert "user.ip_address" not in child_span["attributes"]

tests/integrations/wsgi/test_wsgi.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,3 +841,35 @@ def app(environ, start_response):
841841
)
842842
def test_get_request_url_x_forwarded_proto(environ, use_x_forwarded_for, expected_url):
843843
assert get_request_url(environ, use_x_forwarded_for) == expected_url
844+
845+
846+
@pytest.mark.parametrize("send_default_pii", [True, False])
847+
def test_user_ip_address_on_all_spans(sentry_init, capture_items, send_default_pii):
848+
def dogpark(environ, start_response):
849+
with sentry_sdk.traces.start_span(name="child-span"):
850+
pass
851+
start_response("200 OK", [])
852+
return ["Go get the ball! Good dog!"]
853+
854+
sentry_init(
855+
send_default_pii=send_default_pii,
856+
traces_sample_rate=1.0,
857+
_experiments={"trace_lifecycle": "stream"},
858+
)
859+
app = SentryWsgiMiddleware(dogpark)
860+
client = Client(app)
861+
862+
items = capture_items("span")
863+
864+
client.get("/dogs/are/great/", environ_base={"REMOTE_ADDR": "127.0.0.1"})
865+
866+
sentry_sdk.flush()
867+
868+
child_span, server_span = [item.payload for item in items]
869+
870+
if send_default_pii:
871+
assert server_span["attributes"]["user.ip_address"] == "127.0.0.1"
872+
assert child_span["attributes"]["user.ip_address"] == "127.0.0.1"
873+
else:
874+
assert "user.ip_address" not in server_span["attributes"]
875+
assert "user.ip_address" not in child_span["attributes"]

0 commit comments

Comments
 (0)