Skip to content
1 change: 1 addition & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion sentry_sdk/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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()`.

Expand Down Expand Up @@ -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:
Expand Down
7 changes: 6 additions & 1 deletion sentry_sdk/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions tests/integrations/excepthook/test_excepthook.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion tests/integrations/typer/test_typer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()])

Expand Down
4 changes: 2 additions & 2 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
68 changes: 49 additions & 19 deletions tests/test_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -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", "<invalid>", None))
@pytest.mark.parametrize("http2", [True, False] if PY38 else [False])
@pytest.mark.parametrize("http2", [None, False])
def test_transport_works(
capturing_server,
request,
Expand All @@ -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,
)

Expand Down Expand Up @@ -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
Expand All @@ -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()

Expand All @@ -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
Expand All @@ -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",
Expand All @@ -335,15 +329,15 @@ 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


@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
Expand All @@ -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:
Expand All @@ -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"] == []

Expand Down