diff --git a/sentry_sdk/integrations/wsgi.py b/sentry_sdk/integrations/wsgi.py index 70324a3641..4f7b99c35a 100644 --- a/sentry_sdk/integrations/wsgi.py +++ b/sentry_sdk/integrations/wsgi.py @@ -9,8 +9,10 @@ from sentry_sdk.integrations._wsgi_common import ( DEFAULT_HTTP_METHODS_TO_CAPTURE, _filter_headers, + nullcontext, ) from sentry_sdk.sessions import track_session +from sentry_sdk.scope import use_isolation_scope from sentry_sdk.tracing import Transaction, TRANSACTION_SOURCE_ROUTE from sentry_sdk.utils import ( ContextVar, diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py index 1f54fa8e37..35c96fd7c9 100644 --- a/sentry_sdk/tracing_utils.py +++ b/sentry_sdk/tracing_utils.py @@ -36,9 +36,6 @@ from types import FrameType - from sentry_sdk._types import ExcInfo - from threading import Timer - SENTRY_TRACE_REGEX = re.compile( "^[ \t]*" # whitespace @@ -737,12 +734,3 @@ def get_current_span(scope=None): if TYPE_CHECKING: from sentry_sdk.tracing import Span - - -def finish_running_transaction(transaction=None, exc_info=None): - # type: (Optional[sentry_sdk.Transaction], Optional[ExcInfo]) -> None - if transaction is not None and hasattr(transaction, "_ctx_token"): - if exc_info is not None: - transaction.__exit__(*exc_info) - else: - transaction.__exit__(None, None, None) diff --git a/tests/integrations/django/test_basic.py b/tests/integrations/django/test_basic.py index 316fa09d59..5b75bbb6af 100644 --- a/tests/integrations/django/test_basic.py +++ b/tests/integrations/django/test_basic.py @@ -50,7 +50,7 @@ def test_view_exceptions(sentry_init, client, capture_exceptions, capture_events sentry_init(integrations=[DjangoIntegration()], send_default_pii=True) exceptions = capture_exceptions() events = capture_events() - unpack_werkzeug_response(client.get(reverse("view_exc"))) + client.get(reverse("view_exc")) (error,) = exceptions assert isinstance(error, ZeroDivisionError) @@ -71,9 +71,7 @@ def test_ensures_x_forwarded_header_is_honored_in_sdk_when_enabled_in_django( sentry_init(integrations=[DjangoIntegration()], send_default_pii=True) exceptions = capture_exceptions() events = capture_events() - unpack_werkzeug_response( - client.get(reverse("view_exc"), headers={"X_FORWARDED_HOST": "example.com"}) - ) + client.get(reverse("view_exc"), headers={"X_FORWARDED_HOST": "example.com"}) (error,) = exceptions assert isinstance(error, ZeroDivisionError) @@ -92,9 +90,7 @@ def test_ensures_x_forwarded_header_is_not_honored_when_unenabled_in_django( sentry_init(integrations=[DjangoIntegration()], send_default_pii=True) exceptions = capture_exceptions() events = capture_events() - unpack_werkzeug_response( - client.get(reverse("view_exc"), headers={"X_FORWARDED_HOST": "example.com"}) - ) + client.get(reverse("view_exc"), headers={"X_FORWARDED_HOST": "example.com"}) (error,) = exceptions assert isinstance(error, ZeroDivisionError) @@ -106,7 +102,7 @@ def test_ensures_x_forwarded_header_is_not_honored_when_unenabled_in_django( def test_middleware_exceptions(sentry_init, client, capture_exceptions): sentry_init(integrations=[DjangoIntegration()], send_default_pii=True) exceptions = capture_exceptions() - unpack_werkzeug_response(client.get(reverse("middleware_exc"))) + client.get(reverse("middleware_exc")) (error,) = exceptions assert isinstance(error, ZeroDivisionError) @@ -160,7 +156,7 @@ def test_has_trace_if_performance_enabled(sentry_init, client, capture_events): traces_sample_rate=1.0, ) events = capture_events() - unpack_werkzeug_response(client.head(reverse("view_exc_with_msg"))) + client.head(reverse("view_exc_with_msg")) (msg_event, error_event, transaction_event) = events @@ -216,10 +212,8 @@ def test_trace_from_headers_if_performance_enabled(sentry_init, client, capture_ trace_id = "582b43a4192642f0b136d5159a501701" sentry_trace_header = "{}-{}-{}".format(trace_id, "6e8f22c393e68f19", 1) - unpack_werkzeug_response( - client.head( - reverse("view_exc_with_msg"), headers={"sentry-trace": sentry_trace_header} - ) + client.head( + reverse("view_exc_with_msg"), headers={"sentry-trace": sentry_trace_header} ) (msg_event, error_event, transaction_event) = events @@ -936,7 +930,7 @@ def test_render_spans(sentry_init, client, capture_events, render_span_tree): for url, expected_line in views_tests: events = capture_events() - unpack_werkzeug_response(client.get(url)) + client.get(url) transaction = events[0] assert expected_line in render_span_tree(transaction) @@ -980,7 +974,7 @@ def test_middleware_spans(sentry_init, client, capture_events, render_span_tree) ) events = capture_events() - unpack_werkzeug_response(client.get(reverse("message"))) + client.get(reverse("message")) message, transaction = events @@ -997,7 +991,7 @@ def test_middleware_spans_disabled(sentry_init, client, capture_events): ) events = capture_events() - unpack_werkzeug_response(client.get(reverse("message"))) + client.get(reverse("message")) message, transaction = events @@ -1021,7 +1015,7 @@ def test_signals_spans(sentry_init, client, capture_events, render_span_tree): ) events = capture_events() - unpack_werkzeug_response(client.get(reverse("message"))) + client.get(reverse("message")) message, transaction = events @@ -1044,7 +1038,7 @@ def test_signals_spans_disabled(sentry_init, client, capture_events): ) events = capture_events() - unpack_werkzeug_response(client.get(reverse("message"))) + client.get(reverse("message")) message, transaction = events @@ -1074,7 +1068,7 @@ def test_signals_spans_filtering(sentry_init, client, capture_events, render_spa ) events = capture_events() - unpack_werkzeug_response(client.get(reverse("send_myapp_custom_signal"))) + client.get(reverse("send_myapp_custom_signal")) (transaction,) = events @@ -1202,7 +1196,7 @@ def test_span_origin(sentry_init, client, capture_events): ) events = capture_events() - unpack_werkzeug_response(client.get(reverse("view_with_signal"))) + client.get(reverse("view_with_signal")) (transaction,) = events @@ -1232,9 +1226,9 @@ def test_transaction_http_method_default(sentry_init, client, capture_events): ) events = capture_events() - unpack_werkzeug_response(client.get(reverse("nomessage"))) - unpack_werkzeug_response(client.options(reverse("nomessage"))) - unpack_werkzeug_response(client.head(reverse("nomessage"))) + client.get(reverse("nomessage")) + client.options(reverse("nomessage")) + client.head(reverse("nomessage")) (event,) = events @@ -1258,9 +1252,9 @@ def test_transaction_http_method_custom(sentry_init, client, capture_events): ) events = capture_events() - unpack_werkzeug_response(client.get(reverse("nomessage"))) - unpack_werkzeug_response(client.options(reverse("nomessage"))) - unpack_werkzeug_response(client.head(reverse("nomessage"))) + client.get("/nomessage") + client.options("/nomessage") + client.head("/nomessage") assert len(events) == 2 diff --git a/tests/integrations/flask/test_flask.py b/tests/integrations/flask/test_flask.py index ef9f8351db..4e92df7e7c 100644 --- a/tests/integrations/flask/test_flask.py +++ b/tests/integrations/flask/test_flask.py @@ -394,8 +394,6 @@ def index(): client = app.test_client() response = client.post("/", data=data) assert response.status_code == 200 - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - response.close() event, transaction_event = events @@ -748,8 +746,6 @@ def hi_tx(): with app.test_client() as client: response = client.get("/message_tx") assert response.status_code == 200 - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - response.close() message_event, transaction_event = events @@ -944,9 +940,7 @@ def test_response_status_code_not_found_in_transaction_context( envelopes = capture_envelopes() client = app.test_client() - response = client.get("/not-existing-route") - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - response.close() + client.get("/not-existing-route") sentry_sdk.get_client().flush() @@ -991,21 +985,14 @@ def test_transaction_http_method_default( events = capture_events() client = app.test_client() - response = client.get("/nomessage") assert response.status_code == 200 - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - response.close() response = client.options("/nomessage") assert response.status_code == 200 - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - response.close() response = client.head("/nomessage") assert response.status_code == 200 - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - response.close() (event,) = events @@ -1035,21 +1022,14 @@ def test_transaction_http_method_custom( events = capture_events() client = app.test_client() - response = client.get("/nomessage") assert response.status_code == 200 - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - response.close() response = client.options("/nomessage") assert response.status_code == 200 - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - response.close() response = client.head("/nomessage") assert response.status_code == 200 - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - response.close() assert len(events) == 2 diff --git a/tests/integrations/strawberry/test_strawberry.py b/tests/integrations/strawberry/test_strawberry.py index 0aab78f443..7b40b238d2 100644 --- a/tests/integrations/strawberry/test_strawberry.py +++ b/tests/integrations/strawberry/test_strawberry.py @@ -198,10 +198,7 @@ def test_capture_request_if_available_and_send_pii_is_on( client = client_factory(schema) query = "query ErrorQuery { error }" - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - client.post( - "/graphql", json={"query": query, "operationName": "ErrorQuery"} - ).close() + client.post("/graphql", json={"query": query, "operationName": "ErrorQuery"}) assert len(events) == 1 @@ -256,10 +253,7 @@ def test_do_not_capture_request_if_send_pii_is_off( client = client_factory(schema) query = "query ErrorQuery { error }" - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - client.post( - "/graphql", json={"query": query, "operationName": "ErrorQuery"} - ).close() + client.post("/graphql", json={"query": query, "operationName": "ErrorQuery"}) assert len(events) == 1 @@ -299,8 +293,7 @@ def test_breadcrumb_no_operation_name( client = client_factory(schema) query = "{ error }" - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - client.post("/graphql", json={"query": query}).close() + client.post("/graphql", json={"query": query}) assert len(events) == 1 @@ -339,10 +332,7 @@ def test_capture_transaction_on_error( client = client_factory(schema) query = "query ErrorQuery { error }" - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - client.post( - "/graphql", json={"query": query, "operationName": "ErrorQuery"} - ).close() + client.post("/graphql", json={"query": query, "operationName": "ErrorQuery"}) assert len(events) == 2 (_, transaction_event) = events @@ -419,10 +409,7 @@ def test_capture_transaction_on_success( client = client_factory(schema) query = "query GreetingQuery { hello }" - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - client.post( - "/graphql", json={"query": query, "operationName": "GreetingQuery"} - ).close() + client.post("/graphql", json={"query": query, "operationName": "GreetingQuery"}) assert len(events) == 1 (transaction_event,) = events @@ -499,8 +486,7 @@ def test_transaction_no_operation_name( client = client_factory(schema) query = "{ hello }" - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - client.post("/graphql", json={"query": query}).close() + client.post("/graphql", json={"query": query}) assert len(events) == 1 (transaction_event,) = events @@ -580,8 +566,7 @@ def test_transaction_mutation( client = client_factory(schema) query = 'mutation Change { change(attribute: "something") }' - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - client.post("/graphql", json={"query": query}).close() + client.post("/graphql", json={"query": query}) assert len(events) == 1 (transaction_event,) = events @@ -656,8 +641,7 @@ def test_handle_none_query_gracefully( client_factory = request.getfixturevalue(client_factory) client = client_factory(schema) - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - client.post("/graphql", json={}).close() + client.post("/graphql", json={}) assert len(events) == 0, "expected no events to be sent to Sentry" @@ -689,8 +673,7 @@ def test_span_origin( client = client_factory(schema) query = 'mutation Change { change(attribute: "something") }' - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - client.post("/graphql", json={"query": query}).close() + client.post("/graphql", json={"query": query}) (event,) = events @@ -732,10 +715,7 @@ def test_span_origin2( client = client_factory(schema) query = "query GreetingQuery { hello }" - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - client.post( - "/graphql", json={"query": query, "operationName": "GreetingQuery"} - ).close() + client.post("/graphql", json={"query": query, "operationName": "GreetingQuery"}) (event,) = events @@ -777,8 +757,7 @@ def test_span_origin3( client = client_factory(schema) query = "subscription { messageAdded { content } }" - # Close the response to ensure the WSGI cycle is complete and the transaction is finished - client.post("/graphql", json={"query": query}).close() + client.post("/graphql", json={"query": query}) (event,) = events diff --git a/tests/integrations/wsgi/test_wsgi.py b/tests/integrations/wsgi/test_wsgi.py index 8528d28f5c..5aad355277 100644 --- a/tests/integrations/wsgi/test_wsgi.py +++ b/tests/integrations/wsgi/test_wsgi.py @@ -1,4 +1,3 @@ -import time from collections import Counter from datetime import datetime from unittest import mock @@ -493,48 +492,3 @@ def dogpark(environ, start_response): (event,) = events assert event["contexts"]["trace"]["origin"] == "auto.dogpark.deluxe" - - -def test_long_running_transaction_finished(sentry_init, capture_events): - """ - Test that a long running transaction is finished after the maximum duration, - no matter if the response is still being generated. - """ - # we allow transactions to be 0.5 seconds as a maximum - new_max_duration = 0.5 - - with mock.patch.object( - sentry_sdk.integrations.wsgi, - "MAX_TRANSACTION_DURATION_SECONDS", - new_max_duration, - ): - - def generate_content(): - # This response will take 1.5 seconds to generate - for _ in range(15): - time.sleep(0.1) - yield "ok" - - def long_running_app(environ, start_response): - start_response("200 OK", []) - return generate_content() - - sentry_init(send_default_pii=True, traces_sample_rate=1.0) - app = SentryWsgiMiddleware(long_running_app) - - events = capture_events() - - client = Client(app) - response = client.get("/") - _ = response.get_data() - - (transaction,) = events - - transaction_duration = ( - datetime.fromisoformat(transaction["timestamp"]) - - datetime.fromisoformat(transaction["start_timestamp"]) - ).total_seconds() - assert ( - transaction_duration - <= new_max_duration * 1.02 # we allow 2% margin for processing the request - ), "Long running transaction has not been finished after a set maximum duration"