diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 20ae39c83a..9bad8aa7db 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -1202,6 +1202,13 @@ class SPANDATA: Example: ["AtexitIntegration", "StdlibIntegration"] """ + USER_AGENT_ORIGINAL = "user_agent.original" + """ + Value of the HTTP User-Agent header sent by the client. + + Used in inbound filters. + """ + class SPANSTATUS: """ diff --git a/sentry_sdk/integrations/wsgi.py b/sentry_sdk/integrations/wsgi.py index ab04308f6e..db1ee06a4e 100644 --- a/sentry_sdk/integrations/wsgi.py +++ b/sentry_sdk/integrations/wsgi.py @@ -138,6 +138,10 @@ def __call__( forwarded_host_header=environ.get("HTTP_X_FORWARDED_HOST"), ), ) + if environ.get("HTTP_USER_AGENT"): + scope.set_attribute( + SPANDATA.USER_AGENT_ORIGINAL, environ["HTTP_USER_AGENT"] + ) method = environ.get("REQUEST_METHOD", "").upper() diff --git a/tests/integrations/wsgi/test_wsgi.py b/tests/integrations/wsgi/test_wsgi.py index c0556aa37a..2cda3a1b98 100644 --- a/tests/integrations/wsgi/test_wsgi.py +++ b/tests/integrations/wsgi/test_wsgi.py @@ -1361,7 +1361,7 @@ def test_error_event_no_user_ip_address_without_remote_addr( ), ], ) -def test_is_localhost_span_attribute( +def test_is_localhost_attribute( sentry_init, capture_items, client_kwargs, is_localhost ): def dogpark(environ, start_response): @@ -1384,3 +1384,30 @@ def dogpark(environ, start_response): assert server_span["attributes"]["sentry.is_localhost"] is is_localhost assert child_span["attributes"]["sentry.is_localhost"] is is_localhost + + +def test_user_agent_attribute(sentry_init, capture_items): + def dogpark(environ, start_response): + with sentry_sdk.traces.start_span(name="child-span"): + pass + start_response("200 OK", []) + return ["woof"] + + sentry_init( + traces_sample_rate=1.0, + trace_lifecycle="stream", + ) + + app = SentryWsgiMiddleware(dogpark) + client = Client(app) + + items = capture_items("span") + + client.get("/dogs/", 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"