Skip to content

Commit 564c6c1

Browse files
authored
feat(asgi): Set user_agent.original for inbound filters (#7794)
Part 2 of https://linear.app/getsentry/issue/PY-2898/implement-user-agentoriginal Also deduped some ASGI app setup code in the tests
1 parent 4ec233e commit 564c6c1

2 files changed

Lines changed: 68 additions & 48 deletions

File tree

‎sentry_sdk/integrations/asgi.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,11 @@ async def _run_app(
246246
),
247247
)
248248

249+
if headers.get("user-agent"):
250+
sentry_scope.set_attribute(
251+
SPANDATA.USER_AGENT_ORIGINAL, headers["user-agent"]
252+
)
253+
249254
ty = scope["type"]
250255
(
251256
transaction_name,

‎tests/integrations/asgi/test_asgi.py‎

Lines changed: 63 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,33 @@ async def app(scope, receive, send):
156156
return app
157157

158158

159+
@pytest.fixture
160+
def asgi3_app_with_span():
161+
async def app(scope, receive, send):
162+
if scope["type"] == "lifespan":
163+
while True:
164+
message = await receive()
165+
if message["type"] == "lifespan.startup":
166+
await send({"type": "lifespan.startup.complete"})
167+
elif message["type"] == "lifespan.shutdown":
168+
await send({"type": "lifespan.shutdown.complete"})
169+
return
170+
171+
with sentry_sdk.traces.start_span(name="child-span"):
172+
pass
173+
174+
await send(
175+
{
176+
"type": "http.response.start",
177+
"status": 200,
178+
"headers": [[b"content-type", b"text/plain"]],
179+
}
180+
)
181+
await send({"type": "http.response.body", "body": b"Hello, world!"})
182+
183+
return app
184+
185+
159186
def test_invalid_transaction_style(asgi3_app):
160187
with pytest.raises(ValueError) as exp:
161188
SentryAsgiMiddleware(asgi3_app, transaction_style="URL")
@@ -1441,40 +1468,20 @@ async def test_user_ip_address_on_all_spans(
14411468
capture_items,
14421469
init_kwargs,
14431470
expect_ip,
1471+
asgi3_app_with_span,
14441472
):
1445-
async def app(scope, receive, send):
1446-
if scope["type"] == "lifespan":
1447-
while True:
1448-
message = await receive()
1449-
if message["type"] == "lifespan.startup":
1450-
await send({"type": "lifespan.startup.complete"})
1451-
elif message["type"] == "lifespan.shutdown":
1452-
await send({"type": "lifespan.shutdown.complete"})
1453-
return
1454-
1455-
with sentry_sdk.traces.start_span(name="child-span"):
1456-
pass
1457-
1458-
await send(
1459-
{
1460-
"type": "http.response.start",
1461-
"status": 200,
1462-
"headers": [[b"content-type", b"text/plain"]],
1463-
}
1464-
)
1465-
await send({"type": "http.response.body", "body": b"Hello, world!"})
1466-
14671473
kwargs = dict(init_kwargs)
14681474
sentry_init(
14691475
trace_lifecycle="stream",
14701476
traces_sample_rate=1.0,
14711477
**kwargs,
14721478
)
1473-
sentry_app = SentryAsgiMiddleware(app)
1479+
1480+
app = SentryAsgiMiddleware(asgi3_app_with_span)
14741481

14751482
async def wrapped_app(scope, receive, send):
14761483
scope["client"] = ("127.0.0.1", 0)
1477-
await sentry_app(scope, receive, send)
1484+
await app(scope, receive, send)
14781485

14791486
async with TestClient(wrapped_app) as client:
14801487
items = capture_items("span")
@@ -1517,31 +1524,14 @@ async def test_is_localhost_attribute(
15171524
server,
15181525
host_header,
15191526
is_localhost,
1527+
asgi3_app_with_span,
15201528
):
1521-
async def app(scope, receive, send):
1522-
if scope["type"] == "lifespan":
1523-
while True:
1524-
message = await receive()
1525-
if message["type"] == "lifespan.startup":
1526-
await send({"type": "lifespan.startup.complete"})
1527-
elif message["type"] == "lifespan.shutdown":
1528-
await send({"type": "lifespan.shutdown.complete"})
1529-
return
1530-
1531-
with sentry_sdk.traces.start_span(name="child-span"):
1532-
pass
1533-
1534-
await send(
1535-
{
1536-
"type": "http.response.start",
1537-
"status": 200,
1538-
"headers": [[b"content-type", b"text/plain"]],
1539-
}
1540-
)
1541-
await send({"type": "http.response.body", "body": b"Hello, world!"})
1529+
sentry_init(
1530+
traces_sample_rate=1.0,
1531+
trace_lifecycle="stream",
1532+
)
15421533

1543-
sentry_init(traces_sample_rate=1.0, trace_lifecycle="stream")
1544-
sentry_app = SentryAsgiMiddleware(app)
1534+
app = SentryAsgiMiddleware(asgi3_app_with_span)
15451535

15461536
async def wrapped_app(scope, receive, send):
15471537
if scope["type"] != "lifespan":
@@ -1551,7 +1541,7 @@ async def wrapped_app(scope, receive, send):
15511541
if host_header is not None:
15521542
scope["headers"].append((b"host", host_header))
15531543

1554-
await sentry_app(scope, receive, send)
1544+
await app(scope, receive, send)
15551545

15561546
async with TestClient(wrapped_app) as client:
15571547
items = capture_items("span")
@@ -1563,3 +1553,28 @@ async def wrapped_app(scope, receive, send):
15631553

15641554
assert server_span["attributes"]["sentry.is_localhost"] is is_localhost
15651555
assert child_span["attributes"]["sentry.is_localhost"] is is_localhost
1556+
1557+
1558+
@pytest.mark.asyncio
1559+
async def test_user_agent_original_attribute(
1560+
sentry_init,
1561+
capture_items,
1562+
asgi3_app_with_span,
1563+
):
1564+
sentry_init(
1565+
traces_sample_rate=1.0,
1566+
trace_lifecycle="stream",
1567+
)
1568+
1569+
app = SentryAsgiMiddleware(asgi3_app_with_span)
1570+
1571+
async with TestClient(app) as client:
1572+
items = capture_items("span")
1573+
await client.get("/some_url", headers={"User-Agent": "TestBrowser/1.0"})
1574+
1575+
sentry_sdk.flush()
1576+
1577+
child_span, server_span = [item.payload for item in items]
1578+
1579+
assert server_span["attributes"]["user_agent.original"] == "TestBrowser/1.0"
1580+
assert child_span["attributes"]["user_agent.original"] == "TestBrowser/1.0"

0 commit comments

Comments
 (0)