diff --git a/sentry_sdk/integrations/quart.py b/sentry_sdk/integrations/quart.py index 675a225057..cf9c492dcb 100644 --- a/sentry_sdk/integrations/quart.py +++ b/sentry_sdk/integrations/quart.py @@ -13,8 +13,6 @@ from sentry_sdk.scope import should_send_default_pii from sentry_sdk.traces import SOURCE_FOR_STYLE as SEGMENT_SOURCE_FOR_STYLE from sentry_sdk.traces import StreamedSpan, get_current_span -from sentry_sdk.tracing import SOURCE_FOR_STYLE as TRANSACTION_SOURCE_FOR_STYLE -from sentry_sdk.tracing_utils import has_span_streaming_enabled from sentry_sdk.utils import ( capture_internal_exceptions, ensure_integration_enabled, @@ -119,15 +117,10 @@ def decorator(old_func: "Any") -> "Any": @wraps(old_func) @ensure_integration_enabled(QuartIntegration, old_func) def _sentry_func(*args: "Any", **kwargs: "Any") -> "Any": - client = sentry_sdk.get_client() - if has_span_streaming_enabled(client.options): - span = get_current_span() - if span is not None and hasattr(span, "_segment"): - span._segment._update_active_thread() - else: - current_scope = sentry_sdk.get_current_scope() - if current_scope.transaction is not None: - current_scope.transaction.update_active_thread() + span = get_current_span() + + if span is not None and hasattr(span, "_segment"): + span._segment._update_active_thread() return old_func(*args, **kwargs) @@ -149,11 +142,7 @@ def _set_transaction_name_and_source( "endpoint": request.url_rule.endpoint, } - source = ( - SEGMENT_SOURCE_FOR_STYLE[transaction_style] - if has_span_streaming_enabled(sentry_sdk.get_client().options) - else TRANSACTION_SOURCE_FOR_STYLE[transaction_style] - ) + source = SEGMENT_SOURCE_FOR_STYLE[transaction_style] scope.set_transaction_name( name=name_for_style[transaction_style], @@ -191,79 +180,50 @@ async def _request_websocket_started(app: "Quart", **kwargs: "Any") -> None: scope = sentry_sdk.get_isolation_scope() - if has_span_streaming_enabled(sentry_sdk.get_client().options): - current_span = get_current_span() - if type(current_span) is StreamedSpan: - segment = current_span._segment + current_span = get_current_span() + if type(current_span) is StreamedSpan: + segment = current_span._segment - segment.set_attribute("http.request.method", request_websocket.method) - header_attributes: "dict[str, Any]" = {} + segment.set_attribute("http.request.method", request_websocket.method) + header_attributes: "dict[str, Any]" = {} - for header, header_value in _filter_headers( - dict(request_websocket.headers), use_annotated_value=False - ).items(): - header_attributes[f"http.request.header.{header.lower()}"] = ( - header_value - ) + for header, header_value in _filter_headers( + dict(request_websocket.headers), use_annotated_value=False + ).items(): + header_attributes[f"http.request.header.{header.lower()}"] = header_value - segment.set_attributes(header_attributes) + segment.set_attributes(header_attributes) - client_options = sentry_sdk.get_client().options - filtered_query_string = None - if has_data_collection_enabled(client_options): - query_string = request_websocket.query_string.decode( - "utf-8", errors="replace" - ) - if query_string: - filtered_query_string = ( - _apply_data_collection_filtering_to_query_string( - query_string=query_string, - behaviour=client_options["data_collection"][ - "url_query_params" - ], - ) + client_options = sentry_sdk.get_client().options + filtered_query_string = None + if has_data_collection_enabled(client_options): + query_string = request_websocket.query_string.decode( + "utf-8", errors="replace" + ) + if query_string: + filtered_query_string = ( + _apply_data_collection_filtering_to_query_string( + query_string=query_string, + behaviour=client_options["data_collection"]["url_query_params"], ) - if filtered_query_string: - segment.set_attribute( - "url.query", - filtered_query_string, - ) - - parsed_url = parse_url(request_websocket.url) - segment.set_attribute( - "url.full", - f"{parsed_url.url}?{filtered_query_string}" - if filtered_query_string - else parsed_url.url, ) + if filtered_query_string: + segment.set_attribute( + "url.query", + filtered_query_string, + ) - if client_options["data_collection"]["user_info"]: - user_properties = {} - - if len(request_websocket.access_route) >= 1: - segment.set_attribute( - "client.address", request_websocket.access_route[0] - ) - user_properties["ip_address"] = request_websocket.access_route[ - 0 - ] - - current_user_id = _get_current_user_id_from_quart() - if current_user_id: - user_properties["id"] = current_user_id - - if user_properties: - existing_user_properties = scope._user or {} - scope.set_user({**existing_user_properties, **user_properties}) - - elif should_send_default_pii(): - segment.set_attribute("url.full", request_websocket.url) - segment.set_attribute( - "url.query", - request_websocket.query_string.decode("utf-8", errors="replace"), - ) + parsed_url = parse_url(request_websocket.url) + segment.set_attribute( + "url.full", + f"{parsed_url.url}?{filtered_query_string}" + if filtered_query_string + else parsed_url.url, + ) + if client_options["data_collection"]["user_info"]: user_properties = {} + if len(request_websocket.access_route) >= 1: segment.set_attribute( "client.address", request_websocket.access_route[0] @@ -278,6 +238,28 @@ async def _request_websocket_started(app: "Quart", **kwargs: "Any") -> None: existing_user_properties = scope._user or {} scope.set_user({**existing_user_properties, **user_properties}) + elif should_send_default_pii(): + segment.set_attribute("url.full", request_websocket.url) + segment.set_attribute( + "url.query", + request_websocket.query_string.decode("utf-8", errors="replace"), + ) + + user_properties = {} + if len(request_websocket.access_route) >= 1: + segment.set_attribute( + "client.address", request_websocket.access_route[0] + ) + user_properties["ip_address"] = request_websocket.access_route[0] + + current_user_id = _get_current_user_id_from_quart() + if current_user_id: + user_properties["id"] = current_user_id + + if user_properties: + existing_user_properties = scope._user or {} + scope.set_user({**existing_user_properties, **user_properties}) + evt_processor = _make_request_event_processor(app, request_websocket, integration) scope.add_event_processor(evt_processor) diff --git a/tests/integrations/quart/test_quart.py b/tests/integrations/quart/test_quart.py index 74c63dc27c..fbf230dba4 100644 --- a/tests/integrations/quart/test_quart.py +++ b/tests/integrations/quart/test_quart.py @@ -141,40 +141,6 @@ async def test_has_context(sentry_init, capture_events): assert event["request"]["url"] == "http://localhost/message" -@pytest.mark.asyncio -@pytest.mark.parametrize( - "url,transaction_style,expected_transaction,expected_source", - [ - ("/message", "endpoint", "hi", "component"), - ("/message", "url", "/message", "route"), - ("/message/123456", "endpoint", "hi_with_id", "component"), - ("/message/123456", "url", "/message/", "route"), - ], -) -async def test_transaction_style( - sentry_init, - capture_events, - url, - transaction_style, - expected_transaction, - expected_source, -): - sentry_init( - integrations=[ - quart_sentry.QuartIntegration(transaction_style=transaction_style) - ] - ) - app = quart_app_factory() - events = capture_events() - - client = app.test_client() - response = await client.get(url) - assert response.status_code == 200 - - (event,) = events - assert event["transaction"] == expected_transaction - - @pytest.mark.asyncio async def test_http_route( sentry_init, @@ -570,8 +536,12 @@ async def zerodivision(e): @pytest.mark.asyncio -async def test_tracing_success(sentry_init, capture_events): - sentry_init(traces_sample_rate=1.0, integrations=[quart_sentry.QuartIntegration()]) +async def test_tracing_success(sentry_init, capture_items): + sentry_init( + traces_sample_rate=1.0, + integrations=[quart_sentry.QuartIntegration()], + trace_lifecycle="stream", + ) app = quart_app_factory() @app.before_request @@ -584,31 +554,34 @@ async def hi_tx(): capture_message("hi") return "ok" - events = capture_events() + items = capture_items("span", "event") async with app.test_client() as client: response = await client.get("/message_tx") assert response.status_code == 200 - message_event, transaction_event = events + sentry_sdk.flush() + + message, span = [item.payload for item in items] - assert transaction_event["type"] == "transaction" - assert transaction_event["transaction"] == "hi_tx" - assert transaction_event["tags"]["view"] == "yes" - assert transaction_event["tags"]["before_request"] == "yes" + assert span["name"] == "hi_tx" - assert message_event["message"] == "hi" - assert message_event["transaction"] == "hi_tx" - assert message_event["tags"]["view"] == "yes" - assert message_event["tags"]["before_request"] == "yes" + assert message["message"] == "hi" + assert message["transaction"] == "hi_tx" + assert message["tags"]["view"] == "yes" + assert message["tags"]["before_request"] == "yes" @pytest.mark.asyncio -async def test_tracing_error(sentry_init, capture_events): - sentry_init(traces_sample_rate=1.0, integrations=[quart_sentry.QuartIntegration()]) +async def test_tracing_error(sentry_init, capture_items): + sentry_init( + traces_sample_rate=1.0, + integrations=[quart_sentry.QuartIntegration()], + trace_lifecycle="stream", + ) app = quart_app_factory() - events = capture_events() + items = capture_items("span", "event") @app.route("/error") async def error(): @@ -618,10 +591,11 @@ async def error(): response = await client.get("/error") assert response.status_code == 500 - error_event, transaction_event = events + sentry_sdk.flush() + + error_event, span = [item.payload for item in items] - assert transaction_event["type"] == "transaction" - assert transaction_event["transaction"] == "error" + assert span["name"] == "error" assert error_event["transaction"] == "error" (exception,) = error_event["exception"]["values"] @@ -657,24 +631,27 @@ async def dispatch_request(self): @pytest.mark.asyncio -async def test_span_origin(sentry_init, capture_events): +async def test_span_origin(sentry_init, capture_items): sentry_init( integrations=[quart_sentry.QuartIntegration()], traces_sample_rate=1.0, + trace_lifecycle="stream", ) app = quart_app_factory() - events = capture_events() + items = capture_items("span") client = app.test_client() await client.get("/message") - (_, event) = events + sentry_sdk.flush() + + (span,) = [item.payload for item in items] - assert event["contexts"]["trace"]["origin"] == "auto.http.quart" + assert span["attributes"]["sentry.origin"] == "auto.http.quart" @pytest.mark.asyncio -async def test_span_streaming_basic(sentry_init, capture_items): +async def test_basic(sentry_init, capture_items): sentry_init( integrations=[quart_sentry.QuartIntegration()], traces_sample_rate=1.0, @@ -712,7 +689,7 @@ async def test_span_streaming_basic(sentry_init, capture_items): ("/message/123456", "url", "/message/", "route"), ], ) -async def test_span_streaming_transaction_style( +async def test_transaction_style( sentry_init, capture_items, url, @@ -746,7 +723,7 @@ async def test_span_streaming_transaction_style( @pytest.mark.asyncio -async def test_span_streaming_with_error(sentry_init, capture_items): +async def test_with_error(sentry_init, capture_items): sentry_init( integrations=[quart_sentry.QuartIntegration()], traces_sample_rate=1.0, @@ -789,7 +766,7 @@ async def error(): @pytest.mark.asyncio -async def test_span_streaming_request_attributes_no_pii(sentry_init, capture_items): +async def test_request_attributes_no_pii(sentry_init, capture_items): sentry_init( integrations=[quart_sentry.QuartIntegration()], traces_sample_rate=1.0, @@ -820,7 +797,7 @@ async def test_span_streaming_request_attributes_no_pii(sentry_init, capture_ite @pytest.mark.asyncio -async def test_span_streaming_request_attributes_with_pii(sentry_init, capture_items): +async def test_request_attributes_with_pii(sentry_init, capture_items): sentry_init( integrations=[quart_sentry.QuartIntegration()], traces_sample_rate=1.0, @@ -966,7 +943,7 @@ async def test_span_streaming_request_attributes_with_pii(sentry_init, capture_i ], ) @pytest.mark.asyncio -async def test_span_streaming_sensitive_header_scrubbing( +async def test_sensitive_header_scrubbing( sentry_init, capture_items, options, expected, request ): sentry_init( @@ -1014,9 +991,7 @@ async def test_span_streaming_sensitive_header_scrubbing( @pytest.mark.asyncio -async def test_span_streaming_sensitive_header_without_data_collection( - sentry_init, capture_items -): +async def test_sensitive_header_without_data_collection(sentry_init, capture_items): sentry_init( integrations=[quart_sentry.QuartIntegration()], traces_sample_rate=1.0, @@ -1052,7 +1027,7 @@ async def test_span_streaming_sensitive_header_without_data_collection( @pytest.mark.asyncio @pytest.mark.parametrize("send_default_pii", [True, False]) @pytest.mark.parametrize("user_id", [None, "42"]) -async def test_span_streaming_quart_auth_user_id( +async def test_quart_auth_user_id( send_default_pii, sentry_init, user_id, @@ -1157,7 +1132,7 @@ async def login(): @pytest.mark.asyncio @pytest.mark.parametrize("init_kwargs, expect_user_info", QUART_USER_INFO_CASES) -async def test_span_streaming_quart_auth_user_id_data_collection( +async def test_quart_auth_user_id_data_collection( sentry_init, capture_items, init_kwargs, @@ -1202,7 +1177,7 @@ async def login(): @pytest.mark.asyncio @pytest.mark.parametrize("init_kwargs, expect_user_info", QUART_USER_INFO_CASES) -async def test_span_streaming_request_attributes_data_collection( +async def test_request_attributes_data_collection( sentry_init, capture_items, init_kwargs, expect_user_info ): kwargs = dict(init_kwargs) @@ -1235,7 +1210,7 @@ async def test_span_streaming_request_attributes_data_collection( @pytest.mark.asyncio -async def test_span_streaming_sensitive_header_passthrough_with_pii_and_no_data_collection( +async def test_sensitive_header_passthrough_with_pii_and_no_data_collection( sentry_init, capture_items ): sentry_init( @@ -1340,7 +1315,7 @@ async def test_span_streaming_sensitive_header_passthrough_with_pii_and_no_data_ @pytest.mark.parametrize( "init_kwargs, expected_query", _QUERY_PARAM_DATA_COLLECTION_CASES ) -async def test_span_streaming_url_query_data_collection( +async def test_url_query_data_collection( sentry_init, capture_items, init_kwargs, expected_query ): kwargs = dict(init_kwargs) @@ -1387,9 +1362,7 @@ async def test_span_streaming_url_query_data_collection( @pytest.mark.asyncio -async def test_span_streaming_url_query_multi_and_blank_values( - sentry_init, capture_items -): +async def test_url_query_multi_and_blank_values(sentry_init, capture_items): sentry_init( integrations=[quart_sentry.QuartIntegration()], traces_sample_rate=1.0,