Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions sentry_sdk/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,15 @@ def _parse_rate_limits(
continue


class TransportResponseError(Exception):
"""Raised when the HTTP transport receives an unexpected response status.

A non-2xx status (other than 429 rate limiting) means the envelope was not
accepted. Raising lets custom/wrapper transports detect the failed
delivery and fail over to another route instead of assuming success.
"""


class HttpTransportCore(Transport):
"""Shared base class for sync and async transports."""

Expand Down Expand Up @@ -404,6 +413,13 @@ def _handle_response(
self._handle_request_error(
envelope=envelope, loss_reason="status_{}".format(response.status)
)
# Surface the failed delivery so custom/wrapper transports can
# catch it and fail over (e.g. a 403/404 from a network middlebox).
# 429 is handled via rate limits above and 413 is a deliberate
# drop, so neither raises here.
raise TransportResponseError(
"Unexpected status code: {}".format(response.status)
)

def _update_headers(
self: "Self",
Expand Down
37 changes: 37 additions & 0 deletions tests/test_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -1110,3 +1110,40 @@ async def send_message(i):
# New request should be dropped due to rate limiting
assert len(capturing_server.captured) == 0
await client.close_async(timeout=2.0)


from sentry_sdk.consts import EndpointType
from sentry_sdk.transport import TransportResponseError


@pytest.mark.parametrize("status_code", [403, 404, 500])
def test_unexpected_status_code_raises(capturing_server, make_client, status_code):
# https://github.com/getsentry/sentry-python/issues/7844
# A non-2xx response (e.g. 403/404 from a network middlebox) is a failed
# delivery and must raise, so wrapper transports can catch it and fail
# over instead of assuming the event was sent.
client = make_client()
capturing_server.respond_with(code=status_code)

with pytest.raises(TransportResponseError, match="Unexpected status code"):
client.transport._send_request(
body=b"{}",
headers={},
endpoint_type=EndpointType.ENVELOPE,
)


@pytest.mark.parametrize("status_code", [413, 429])
def test_expected_error_status_codes_do_not_raise(
capturing_server, make_client, status_code
):
# 413 (envelope too large) is a deliberate drop and 429 is handled via
# rate limits; neither should raise.
client = make_client()
capturing_server.respond_with(code=status_code)

client.transport._send_request(
body=b"{}",
headers={},
endpoint_type=EndpointType.ENVELOPE,
)