diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index 073b1ee376..9c35351ebb 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -246,6 +246,11 @@ async def _run_app( ), ) + if headers.get("user-agent"): + sentry_scope.set_attribute( + SPANDATA.USER_AGENT_ORIGINAL, headers["user-agent"] + ) + ty = scope["type"] ( transaction_name, diff --git a/tests/integrations/asgi/test_asgi.py b/tests/integrations/asgi/test_asgi.py index d4d47f7831..139c98868e 100644 --- a/tests/integrations/asgi/test_asgi.py +++ b/tests/integrations/asgi/test_asgi.py @@ -156,6 +156,33 @@ async def app(scope, receive, send): return app +@pytest.fixture +def asgi3_app_with_span(): + async def app(scope, receive, send): + if scope["type"] == "lifespan": + while True: + message = await receive() + if message["type"] == "lifespan.startup": + await send({"type": "lifespan.startup.complete"}) + elif message["type"] == "lifespan.shutdown": + await send({"type": "lifespan.shutdown.complete"}) + return + + with sentry_sdk.traces.start_span(name="child-span"): + pass + + await send( + { + "type": "http.response.start", + "status": 200, + "headers": [[b"content-type", b"text/plain"]], + } + ) + await send({"type": "http.response.body", "body": b"Hello, world!"}) + + return app + + def test_invalid_transaction_style(asgi3_app): with pytest.raises(ValueError) as exp: SentryAsgiMiddleware(asgi3_app, transaction_style="URL") @@ -1441,40 +1468,20 @@ async def test_user_ip_address_on_all_spans( capture_items, init_kwargs, expect_ip, + asgi3_app_with_span, ): - async def app(scope, receive, send): - if scope["type"] == "lifespan": - while True: - message = await receive() - if message["type"] == "lifespan.startup": - await send({"type": "lifespan.startup.complete"}) - elif message["type"] == "lifespan.shutdown": - await send({"type": "lifespan.shutdown.complete"}) - return - - with sentry_sdk.traces.start_span(name="child-span"): - pass - - await send( - { - "type": "http.response.start", - "status": 200, - "headers": [[b"content-type", b"text/plain"]], - } - ) - await send({"type": "http.response.body", "body": b"Hello, world!"}) - kwargs = dict(init_kwargs) sentry_init( trace_lifecycle="stream", traces_sample_rate=1.0, **kwargs, ) - sentry_app = SentryAsgiMiddleware(app) + + app = SentryAsgiMiddleware(asgi3_app_with_span) async def wrapped_app(scope, receive, send): scope["client"] = ("127.0.0.1", 0) - await sentry_app(scope, receive, send) + await app(scope, receive, send) async with TestClient(wrapped_app) as client: items = capture_items("span") @@ -1517,31 +1524,14 @@ async def test_is_localhost_attribute( server, host_header, is_localhost, + asgi3_app_with_span, ): - async def app(scope, receive, send): - if scope["type"] == "lifespan": - while True: - message = await receive() - if message["type"] == "lifespan.startup": - await send({"type": "lifespan.startup.complete"}) - elif message["type"] == "lifespan.shutdown": - await send({"type": "lifespan.shutdown.complete"}) - return - - with sentry_sdk.traces.start_span(name="child-span"): - pass - - await send( - { - "type": "http.response.start", - "status": 200, - "headers": [[b"content-type", b"text/plain"]], - } - ) - await send({"type": "http.response.body", "body": b"Hello, world!"}) + sentry_init( + traces_sample_rate=1.0, + trace_lifecycle="stream", + ) - sentry_init(traces_sample_rate=1.0, trace_lifecycle="stream") - sentry_app = SentryAsgiMiddleware(app) + app = SentryAsgiMiddleware(asgi3_app_with_span) async def wrapped_app(scope, receive, send): if scope["type"] != "lifespan": @@ -1551,7 +1541,7 @@ async def wrapped_app(scope, receive, send): if host_header is not None: scope["headers"].append((b"host", host_header)) - await sentry_app(scope, receive, send) + await app(scope, receive, send) async with TestClient(wrapped_app) as client: items = capture_items("span") @@ -1563,3 +1553,28 @@ async def wrapped_app(scope, receive, send): assert server_span["attributes"]["sentry.is_localhost"] is is_localhost assert child_span["attributes"]["sentry.is_localhost"] is is_localhost + + +@pytest.mark.asyncio +async def test_user_agent_original_attribute( + sentry_init, + capture_items, + asgi3_app_with_span, +): + sentry_init( + traces_sample_rate=1.0, + trace_lifecycle="stream", + ) + + app = SentryAsgiMiddleware(asgi3_app_with_span) + + async with TestClient(app) as client: + items = capture_items("span") + await client.get("/some_url", headers={"User-Agent": "TestBrowser/1.0"}) + + sentry_sdk.flush() + + child_span, server_span = [item.payload for item in items] + + assert server_span["attributes"]["user_agent.original"] == "TestBrowser/1.0" + assert child_span["attributes"]["user_agent.original"] == "TestBrowser/1.0"