diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index c680aada9d..333a7fd598 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -33,6 +33,7 @@ sentry_sdk.init( - The SDK now supports Python 3.7 and higher. - Tag values on event dictionaries, which are passed to `before_send` and `before_send_transaction`, now are always `str` values. Previously, despite tag values being typed as `str`, they often had different values. Therefore, if you have configured any `before_send` and `before_send_transaction` functions which perform some logic based on tag values, you need to check and if needed update those functions to correctly handle `str` values. +- The SDK now uses HTTP/2 for its default transport. This can be disabled via setting `http2=False` in `sentry_sdk.init()`. This change also affects `socket_options` as HTTP/2 requires persistent connections. If you want to disable keep-alive, consider disabling HTTP/2 as passing an empty list to `socket_options` would _not_ disable keep alive. #### Error Capturing diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 4adfcd4338..23a07f18eb 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -77,7 +77,6 @@ class CompressionAlgo(Enum): "transport_compression_level": Optional[int], "transport_compression_algo": Optional[CompressionAlgo], "transport_num_pools": Optional[int], - "transport_http2": Optional[bool], "transport_async": Optional[bool], }, total=False, @@ -971,6 +970,7 @@ def __init__( max_stack_frames: Optional[int] = DEFAULT_MAX_STACK_FRAMES, enable_logs: bool = False, before_send_log: Optional[Callable[[Log, Hint], Optional[Log]]] = None, + http2: Optional[bool] = None, ) -> None: """Initialize the Sentry SDK with the given parameters. All parameters described here can be used in a call to `sentry_sdk.init()`. @@ -1343,6 +1343,8 @@ def __init__( This is relative to the tracing sample rate - e.g. `0.5` means 50% of sampled transactions will be profiled. + :param http2: Defaults to `True`, enables HTTP/2 support for the SDK. + :param profiles_sampler: :param profiler_mode: diff --git a/sentry_sdk/transport.py b/sentry_sdk/transport.py index 6d7e4c4f84..554b01f3f4 100644 --- a/sentry_sdk/transport.py +++ b/sentry_sdk/transport.py @@ -1051,7 +1051,12 @@ def _make_pool( def make_transport(options: Dict[str, Any]) -> Optional[Transport]: ref_transport = options["transport"] - use_http2_transport = options.get("_experiments", {}).get("transport_http2", False) + # We default to using HTTP2 transport if the user also has the required h2 + # library installed (through the subclass check). The reason is h2 not being + # available on py3.7 which we still support. + use_http2_transport = options.get("http2") is not False and not issubclass( + Http2Transport, HttpTransport + ) use_async_transport = options.get("_experiments", {}).get("transport_async", False) async_integration = any( integration.__class__.__name__ == "AsyncioIntegration" diff --git a/tests/integrations/excepthook/test_excepthook.py b/tests/integrations/excepthook/test_excepthook.py index 745f62d818..813f0493c9 100644 --- a/tests/integrations/excepthook/test_excepthook.py +++ b/tests/integrations/excepthook/test_excepthook.py @@ -5,10 +5,10 @@ from textwrap import dedent -TEST_PARAMETERS = [("", "HttpTransport")] +TEST_PARAMETERS = [("http2=False", "HttpTransport")] if sys.version_info >= (3, 8): - TEST_PARAMETERS.append(('_experiments={"transport_http2": True}', "Http2Transport")) + TEST_PARAMETERS.append(("", "Http2Transport")) @pytest.mark.parametrize("options, transport", TEST_PARAMETERS) diff --git a/tests/integrations/typer/test_typer.py b/tests/integrations/typer/test_typer.py index 34ac0a7c8c..9dfda8ef40 100644 --- a/tests/integrations/typer/test_typer.py +++ b/tests/integrations/typer/test_typer.py @@ -26,7 +26,7 @@ def capture_envelope(self, envelope): if event is not None: print(event) - transport.HttpTransport.capture_envelope = capture_envelope + transport.BaseHttpTransport.capture_envelope = capture_envelope init("http://foobar@localhost/123", integrations=[TyperIntegration()]) diff --git a/tests/test_client.py b/tests/test_client.py index 323e51cd10..9474e13f85 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -257,8 +257,8 @@ def test_proxy(monkeypatch, testcase, http2): kwargs = {} - if http2: - kwargs["_experiments"] = {"transport_http2": True} + if not http2: + kwargs["http2"] = False if testcase["arg_http_proxy"] is not None: kwargs["http_proxy"] = testcase["arg_http_proxy"] diff --git a/tests/test_transport.py b/tests/test_transport.py index a8e6c90d00..7c344655b6 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -83,7 +83,7 @@ def mock_transaction_envelope(span_count: int) -> Envelope: @pytest.mark.parametrize("use_pickle", (True, False)) @pytest.mark.parametrize("compression_level", (0, 9, None)) @pytest.mark.parametrize("compression_algo", ("gzip", "br", "", None)) -@pytest.mark.parametrize("http2", [True, False] if PY38 else [False]) +@pytest.mark.parametrize("http2", [None, False]) def test_transport_works( capturing_server, request, @@ -106,11 +106,9 @@ def test_transport_works( if compression_algo is not None: experiments["transport_compression_algo"] = compression_algo - if http2: - experiments["transport_http2"] = True - client = make_client( debug=debug, + http2=http2, _experiments=experiments, ) @@ -245,7 +243,7 @@ def test_transport_num_pools(make_client, num_pools, expected_num_pools): if num_pools is not None: _experiments["transport_num_pools"] = num_pools - client = make_client(_experiments=_experiments) + client = make_client(_experiments=_experiments, http2=False) options = client.transport._get_pool_options() assert options["num_pools"] == expected_num_pools @@ -255,17 +253,13 @@ def test_transport_num_pools(make_client, num_pools, expected_num_pools): "http2", [True, False] if sys.version_info >= (3, 8) else [False] ) def test_two_way_ssl_authentication(make_client, http2): - _experiments = {} - if http2: - _experiments["transport_http2"] = True - current_dir = os.path.dirname(__file__) cert_file = f"{current_dir}/test.pem" key_file = f"{current_dir}/test.key" client = make_client( cert_file=cert_file, key_file=key_file, - _experiments=_experiments, + http2=http2, ) options = client.transport._get_pool_options() @@ -290,20 +284,20 @@ def test_socket_options(make_client): def test_keep_alive_true(make_client): - client = make_client(keep_alive=True) + client = make_client(keep_alive=True, http2=False) options = client.transport._get_pool_options() assert options["socket_options"] == KEEP_ALIVE_SOCKET_OPTIONS def test_keep_alive_on_by_default(make_client): - client = make_client() + client = make_client(http2=False) options = client.transport._get_pool_options() assert "socket_options" not in options def test_default_timeout(make_client): - client = make_client() + client = make_client(http2=False) options = client.transport._get_pool_options() assert "timeout" in options @@ -312,7 +306,7 @@ def test_default_timeout(make_client): @pytest.mark.skipif(not PY38, reason="HTTP2 libraries are only available in py3.8+") def test_default_timeout_http2(make_client): - client = make_client(_experiments={"transport_http2": True}) + client = make_client() with mock.patch( "sentry_sdk.transport.httpcore.ConnectionPool.request", @@ -335,7 +329,7 @@ def test_default_timeout_http2(make_client): @pytest.mark.skipif(not PY38, reason="HTTP2 libraries are only available in py3.8+") def test_http2_with_https_dsn(make_client): - client = make_client(_experiments={"transport_http2": True}) + client = make_client() client.transport.parsed_dsn.scheme = "https" options = client.transport._get_pool_options() assert options["http2"] is True @@ -343,7 +337,7 @@ def test_http2_with_https_dsn(make_client): @pytest.mark.skipif(not PY38, reason="HTTP2 libraries are only available in py3.8+") def test_no_http2_with_http_dsn(make_client): - client = make_client(_experiments={"transport_http2": True}) + client = make_client() client.transport.parsed_dsn.scheme = "http" options = client.transport._get_pool_options() assert options["http2"] is False @@ -356,19 +350,44 @@ def test_socket_options_override_keep_alive(make_client): (socket.SOL_TCP, socket.TCP_KEEPCNT, 6), ] - client = make_client(socket_options=socket_options, keep_alive=False) + client = make_client(socket_options=socket_options, keep_alive=False, http2=False) options = client.transport._get_pool_options() assert options["socket_options"] == socket_options +@pytest.mark.skipif(not PY38, reason="HTTP2 libraries are only available in py3.8+") +def test_socket_options_merge_with_keep_alive_http2(make_client): + socket_options = [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 42), + (socket.SOL_TCP, socket.TCP_KEEPINTVL, 42), + ] + + client = make_client(socket_options=socket_options) + + options = client.transport._get_pool_options() + try: + assert options["socket_options"] == [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 42), + (socket.SOL_TCP, socket.TCP_KEEPINTVL, 42), + (socket.SOL_TCP, socket.TCP_KEEPIDLE, 45), + (socket.SOL_TCP, socket.TCP_KEEPCNT, 6), + ] + except AttributeError: + assert options["socket_options"] == [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 42), + (socket.SOL_TCP, socket.TCP_KEEPINTVL, 42), + (socket.SOL_TCP, socket.TCP_KEEPCNT, 6), + ] + + def test_socket_options_merge_with_keep_alive(make_client): socket_options = [ (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 42), (socket.SOL_TCP, socket.TCP_KEEPINTVL, 42), ] - client = make_client(socket_options=socket_options, keep_alive=True) + client = make_client(socket_options=socket_options, keep_alive=True, http2=False) options = client.transport._get_pool_options() try: @@ -386,12 +405,23 @@ def test_socket_options_merge_with_keep_alive(make_client): ] -def test_socket_options_override_defaults(make_client): +@pytest.mark.skipif(not PY38, reason="HTTP2 libraries are only available in py3.8+") +def test_socket_options_override_defaults_http2(make_client): # If socket_options are set to [], this doesn't mean the user doesn't want # any custom socket_options, but rather that they want to disable the urllib3 # socket option defaults, so we need to set this and not ignore it. client = make_client(socket_options=[]) + options = client.transport._get_pool_options() + assert options["socket_options"] == KEEP_ALIVE_SOCKET_OPTIONS + + +def test_socket_options_override_defaults(make_client): + # If socket_options are set to [], this doesn't mean the user doesn't want + # any custom socket_options, but rather that they want to disable the urllib3 + # socket option defaults, so we need to set this and not ignore it. + client = make_client(http2=False, socket_options=[]) + options = client.transport._get_pool_options() assert options["socket_options"] == []