diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index 270d814bfe..64e65a8cb6 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -148,6 +148,12 @@ def _get_options(*args, **kwargs): if rv["event_scrubber"] is None: rv["event_scrubber"] = EventScrubber() + if rv["socket_options"] and not isinstance(rv["socket_options"], list): + logger.warning( + "Ignoring socket_options because of unexpected format. See urllib3.HTTPConnection.socket_options for the expected format." + ) + rv["socket_options"] = None + return rv diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index fe9736938c..c366d04927 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -14,6 +14,7 @@ from typing import Dict from typing import Any from typing import Sequence + from typing import Tuple from typing_extensions import TypedDict from sentry_sdk.integrations import Integration @@ -260,6 +261,7 @@ def __init__( https_proxy=None, # type: Optional[str] ignore_errors=[], # type: Sequence[Union[type, str]] # noqa: B006 max_request_body_size="medium", # type: str + socket_options=None, # type: Optional[List[Tuple[int, int, int | bytes]]] before_send=None, # type: Optional[EventProcessor] before_breadcrumb=None, # type: Optional[BreadcrumbProcessor] debug=None, # type: Optional[bool] diff --git a/sentry_sdk/transport.py b/sentry_sdk/transport.py index 8eb00bed12..b924ae502a 100644 --- a/sentry_sdk/transport.py +++ b/sentry_sdk/transport.py @@ -1,18 +1,17 @@ from __future__ import print_function import io -import urllib3 -import certifi import gzip import time - from datetime import timedelta from collections import defaultdict +import urllib3 +import certifi + from sentry_sdk.utils import Dsn, logger, capture_internal_exceptions, json_dumps from sentry_sdk.worker import BackgroundWorker from sentry_sdk.envelope import Envelope, Item, PayloadRef - from sentry_sdk._compat import datetime_utcnow from sentry_sdk._types import TYPE_CHECKING @@ -441,12 +440,17 @@ def _send_envelope( def _get_pool_options(self, ca_certs): # type: (Optional[Any]) -> Dict[str, Any] - return { + options = { "num_pools": self._num_pools, "cert_reqs": "CERT_REQUIRED", "ca_certs": ca_certs or certifi.where(), } + if self.options["socket_options"]: + options["socket_options"] = self.options["socket_options"] + + return options + def _in_no_proxy(self, parsed_dsn): # type: (Dsn) -> bool no_proxy = getproxies().get("no") diff --git a/tests/test_transport.py b/tests/test_transport.py index 71c47e04fc..aa471b9081 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -3,14 +3,13 @@ import pickle import gzip import io - +import socket +from collections import namedtuple from datetime import datetime, timedelta import pytest -from collections import namedtuple -from werkzeug.wrappers import Request, Response - from pytest_localserver.http import WSGIServer +from werkzeug.wrappers import Request, Response from sentry_sdk import Hub, Client, add_breadcrumb, capture_message, Scope from sentry_sdk._compat import datetime_utcnow @@ -155,6 +154,19 @@ def test_transport_num_pools(make_client, num_pools, expected_num_pools): assert options["num_pools"] == expected_num_pools +def test_socket_options(make_client): + socket_options = [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + (socket.SOL_TCP, socket.TCP_KEEPINTVL, 10), + (socket.SOL_TCP, socket.TCP_KEEPCNT, 6), + ] + + client = make_client(socket_options=socket_options) + + options = client.transport._get_pool_options([]) + assert options["socket_options"] == socket_options + + def test_transport_infinite_loop(capturing_server, request, make_client): client = make_client( debug=True, @@ -219,7 +231,7 @@ def test_parse_rate_limits(input, expected): assert dict(_parse_rate_limits(input, now=NOW)) == expected -def test_simple_rate_limits(capturing_server, capsys, caplog, make_client): +def test_simple_rate_limits(capturing_server, make_client): client = make_client() capturing_server.respond_with(code=429, headers={"Retry-After": "4"}) @@ -241,7 +253,7 @@ def test_simple_rate_limits(capturing_server, capsys, caplog, make_client): @pytest.mark.parametrize("response_code", [200, 429]) def test_data_category_limits( - capturing_server, capsys, caplog, response_code, make_client, monkeypatch + capturing_server, response_code, make_client, monkeypatch ): client = make_client(send_client_reports=False) @@ -288,7 +300,7 @@ def record_lost_event(reason, data_category=None, item=None): @pytest.mark.parametrize("response_code", [200, 429]) def test_data_category_limits_reporting( - capturing_server, capsys, caplog, response_code, make_client, monkeypatch + capturing_server, response_code, make_client, monkeypatch ): client = make_client(send_client_reports=True) @@ -371,7 +383,7 @@ def intercepting_fetch(*args, **kwargs): @pytest.mark.parametrize("response_code", [200, 429]) def test_complex_limits_without_data_category( - capturing_server, capsys, caplog, response_code, make_client + capturing_server, response_code, make_client ): client = make_client() capturing_server.respond_with(