From bbfb654a66b0cd6854a2a387ffe001fb2a2d0f5c Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Sep 2025 14:22:47 +0200 Subject: [PATCH 01/19] feat: Option to not trace HTTP requests based on status codes --- sentry_sdk/consts.py | 11 +++ sentry_sdk/tracing.py | 31 +++++++++ tests/tracing/test_ignore_status_codes.py | 81 +++++++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 tests/tracing/test_ignore_status_codes.py diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 9e84dc3dd2..1507fd3912 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -919,6 +919,7 @@ def __init__( max_stack_frames=DEFAULT_MAX_STACK_FRAMES, # type: Optional[int] enable_logs=False, # type: bool before_send_log=None, # type: Optional[Callable[[Log, Hint], Optional[Log]]] + trace_ignore_status_codes=[], # type: Sequence[Union[int, Tuple[int, int]]] ): # type: (...) -> None """Initialize the Sentry SDK with the given parameters. All parameters described here can be used in a call to `sentry_sdk.init()`. @@ -1307,6 +1308,16 @@ def __init__( function will be retained. If the function returns None, the log will not be sent to Sentry. + :param trace_ignore_status_codes: An optional property that disables tracing for + HTTP requests with certain response codes. + + The option is a list, where elements are individual response codes, or inclusive + ranges of response codes. Requests are not traced if any code matches or + any provided range contains the response code. + + If `trace_ignore_status_codes` is not provided, requests with any status code + may be traced. + :param _experiments: """ pass diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index a82b99ff4d..5185ea927c 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -30,6 +30,7 @@ from typing import Tuple from typing import Union from typing import TypeVar + from typing import Sequence from typing_extensions import TypedDict, Unpack @@ -970,6 +971,30 @@ def _get_scope_from_finish_args( return scope_or_hub + def _in_http_status_code_range(self, code, code_ranges): + # type: (int, Sequence[Union[int, Tuple[int, int]]]) -> bool + for target in code_ranges: + if isinstance(target, int): + print(code, target, code == target, type(code), type(target)) + if code == target: + return True + continue + + if ( + len(target) != 2 + or not isinstance(target[0], int) + or not isinstance(target[1], int) + ): + logger.warning( + "trace_ignore_status_codes must be a sequences including only integers or pairs of integers." + ) + continue + + if target[0] <= code <= target[1]: + return True + + return False + def finish( self, scope=None, # type: Optional[sentry_sdk.Scope] @@ -1039,6 +1064,12 @@ def finish( super().finish(scope, end_timestamp) + if SPANDATA.HTTP_STATUS_CODE in self._data and self._in_http_status_code_range( + self._data[SPANDATA.HTTP_STATUS_CODE], + client.options["trace_ignore_status_codes"], + ): + self.sampled = False + if not self.sampled: # At this point a `sampled = None` should have already been resolved # to a concrete decision. diff --git a/tests/tracing/test_ignore_status_codes.py b/tests/tracing/test_ignore_status_codes.py new file mode 100644 index 0000000000..a8c11b5b85 --- /dev/null +++ b/tests/tracing/test_ignore_status_codes.py @@ -0,0 +1,81 @@ +import sentry_sdk +from sentry_sdk import start_transaction, start_span + +import pytest + + +def test_no_ignored_codes(sentry_init, capture_events): + sentry_init( + traces_sample_rate=1.0, + ) + events = capture_events() + + with start_transaction(op="http", name="GET /"): + span_or_tx = sentry_sdk.get_current_span() + span_or_tx.set_data("http.response.status_code", 404) + + assert len(events) == 1 + + +@pytest.mark.parametrize("status_code", [200, 404]) +def test_single_code_ignored(sentry_init, capture_events, status_code): + sentry_init( + traces_sample_rate=1.0, + trace_ignore_status_codes=(404,), + ) + events = capture_events() + + with start_transaction(op="http", name="GET /"): + span_or_tx = sentry_sdk.get_current_span() + span_or_tx.set_data("http.response.status_code", status_code) + + if status_code == 404: + assert not events + else: + assert len(events) == 1 + + +@pytest.mark.parametrize("status_code", [200, 305, 307, 399, 404]) +def test_range_ignored(sentry_init, capture_events, status_code): + sentry_init( + traces_sample_rate=1.0, + trace_ignore_status_codes=((305, 399),), + ) + events = capture_events() + + with start_transaction(op="http", name="GET /"): + span_or_tx = sentry_sdk.get_current_span() + span_or_tx.set_data("http.response.status_code", status_code) + + if 305 <= status_code <= 399: + assert not events + else: + assert len(events) == 1 + + +@pytest.mark.parametrize("status_code", [200, 301, 303, 355, 404]) +def test_variety_ignored(sentry_init, capture_events, status_code): + sentry_init( + traces_sample_rate=1.0, + trace_ignore_status_codes=( + 301, + 302, + 303, + (305, 399), + (401, 404), + ), + ) + events = capture_events() + + with start_transaction(op="http", name="GET /"): + span_or_tx = sentry_sdk.get_current_span() + span_or_tx.set_data("http.response.status_code", status_code) + + if ( + 301 <= status_code <= 303 + or 305 <= status_code <= 399 + or 401 <= status_code <= 404 + ): + assert not events + else: + assert len(events) == 1 From 7cf3d7cd19a8635554b95bbc0a6236cfc7fb42b6 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Sep 2025 14:43:04 +0200 Subject: [PATCH 02/19] add debug log --- sentry_sdk/tracing.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 5185ea927c..ed1ee661e8 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -975,7 +975,6 @@ def _in_http_status_code_range(self, code, code_ranges): # type: (int, Sequence[Union[int, Tuple[int, int]]]) -> bool for target in code_ranges: if isinstance(target, int): - print(code, target, code == target, type(code), type(target)) if code == target: return True continue @@ -995,6 +994,11 @@ def _in_http_status_code_range(self, code, code_ranges): return False + def _get_log_representation(self): + return "{op}transaction <{name}>".format( + op=("<" + self.op + "> " if self.op else ""), name=self.name + ) + def finish( self, scope=None, # type: Optional[sentry_sdk.Scope] @@ -1068,6 +1072,15 @@ def finish( self._data[SPANDATA.HTTP_STATUS_CODE], client.options["trace_ignore_status_codes"], ): + logger.debug( + "[Tracing] Discarding {transaction_description} because the HTTP status code {status_code} is matched by trace_ignore_status_codes: {trace_ignore_status_codes}".format( + transaction_description=self._get_log_reprensentation(), + status_code=self._data[SPANDATA.HTTP_STATUS_CODE], + trace_ignore_status_codes=client.options[ + "trace_ignore_status_codes" + ], + ) + ) self.sampled = False if not self.sampled: @@ -1217,9 +1230,7 @@ def _set_initial_sampling_decision(self, sampling_context): """ client = sentry_sdk.get_client() - transaction_description = "{op}transaction <{name}>".format( - op=("<" + self.op + "> " if self.op else ""), name=self.name - ) + transaction_description = self._get_log_representation() # nothing to do if tracing is disabled if not has_tracing_enabled(client.options): From 7bc4d62fdf088cb984f5e19dd3f4e2e65ec5fab9 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Sep 2025 14:58:26 +0200 Subject: [PATCH 03/19] improved validation --- sentry_sdk/tracing.py | 27 +++++++------- tests/tracing/test_ignore_status_codes.py | 45 +++++++++++++++++++++-- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index ed1ee661e8..6acd50e187 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -979,18 +979,19 @@ def _in_http_status_code_range(self, code, code_ranges): return True continue - if ( - len(target) != 2 - or not isinstance(target[0], int) - or not isinstance(target[1], int) - ): - logger.warning( - "trace_ignore_status_codes must be a sequences including only integers or pairs of integers." - ) - continue - - if target[0] <= code <= target[1]: - return True + wrong_type_message = "trace_ignore_status_codes must be a list of integers or pairs of integers." + try: + if ( + len(target) == 2 + and isinstance(target[0], int) + and isinstance(target[1], int) + and target[0] <= code <= target[1] + ): + return True + elif not isinstance(target[0], int) or not isinstance(target[1], int): + logger.warning(wrong_type_message) + except (TypeError, IndexError): + logger.warning(wrong_type_message) return False @@ -1074,7 +1075,7 @@ def finish( ): logger.debug( "[Tracing] Discarding {transaction_description} because the HTTP status code {status_code} is matched by trace_ignore_status_codes: {trace_ignore_status_codes}".format( - transaction_description=self._get_log_reprensentation(), + transaction_description=self._get_log_representation(), status_code=self._data[SPANDATA.HTTP_STATUS_CODE], trace_ignore_status_codes=client.options[ "trace_ignore_status_codes" diff --git a/tests/tracing/test_ignore_status_codes.py b/tests/tracing/test_ignore_status_codes.py index a8c11b5b85..b188d526ef 100644 --- a/tests/tracing/test_ignore_status_codes.py +++ b/tests/tracing/test_ignore_status_codes.py @@ -39,7 +39,12 @@ def test_single_code_ignored(sentry_init, capture_events, status_code): def test_range_ignored(sentry_init, capture_events, status_code): sentry_init( traces_sample_rate=1.0, - trace_ignore_status_codes=((305, 399),), + trace_ignore_status_codes=( + ( + 305, + 399, + ), + ), ) events = capture_events() @@ -61,8 +66,14 @@ def test_variety_ignored(sentry_init, capture_events, status_code): 301, 302, 303, - (305, 399), - (401, 404), + ( + 305, + 399, + ), + ( + 401, + 404, + ), ), ) events = capture_events() @@ -79,3 +90,31 @@ def test_variety_ignored(sentry_init, capture_events, status_code): assert not events else: assert len(events) == 1 + + +def test_malformed_argument_ignored(sentry_init, capture_events): + sentry_init( + traces_sample_rate=1.0, + trace_ignore_status_codes=( + 404.0, + "404", + "401-404", + (404,), + ( + "401", + "404", + ), + ( + 401, + 404, + 500, + ), + ), + ) + events = capture_events() + + with start_transaction(op="http", name="GET /"): + span_or_tx = sentry_sdk.get_current_span() + span_or_tx.set_data("http.response.status_code", 404) + + assert len(events) == 1 From ad4e223d52f3652a4f6f5de10e14a162cfd3b258 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Sep 2025 15:15:53 +0200 Subject: [PATCH 04/19] add type annotation --- sentry_sdk/tracing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 6acd50e187..0994f05430 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -996,6 +996,7 @@ def _in_http_status_code_range(self, code, code_ranges): return False def _get_log_representation(self): + # type: () -> str return "{op}transaction <{name}>".format( op=("<" + self.op + "> " if self.op else ""), name=self.name ) From 7eeab46cbdf1080a513191f7659b122d2e45c7f8 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Sep 2025 15:35:08 +0200 Subject: [PATCH 05/19] improve range check --- sentry_sdk/tracing.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 0994f05430..5a63fe15bd 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -981,16 +981,15 @@ def _in_http_status_code_range(self, code, code_ranges): wrong_type_message = "trace_ignore_status_codes must be a list of integers or pairs of integers." try: - if ( - len(target) == 2 - and isinstance(target[0], int) - and isinstance(target[1], int) - and target[0] <= code <= target[1] - ): - return True - elif not isinstance(target[0], int) or not isinstance(target[1], int): + low, high = target + if not isinstance(target[0], int) or not isinstance(target[1], int): logger.warning(wrong_type_message) - except (TypeError, IndexError): + continue + + if low <= code <= high: + return True + + except Exception: logger.warning(wrong_type_message) return False From 7997870fbc5ac4a476edac6b799c72afbc38f1ae Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Sep 2025 15:35:53 +0200 Subject: [PATCH 06/19] . --- sentry_sdk/tracing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 5a63fe15bd..76172ce412 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -982,7 +982,7 @@ def _in_http_status_code_range(self, code, code_ranges): wrong_type_message = "trace_ignore_status_codes must be a list of integers or pairs of integers." try: low, high = target - if not isinstance(target[0], int) or not isinstance(target[1], int): + if not isinstance(low, int) or not isinstance(high, int): logger.warning(wrong_type_message) continue From 82e25d38ddf7596028790e1336d2b3245ef01110 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Sep 2025 16:12:12 +0200 Subject: [PATCH 07/19] improve warning message --- sentry_sdk/tracing.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 76172ce412..929aa51e89 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -982,14 +982,29 @@ def _in_http_status_code_range(self, code, code_ranges): wrong_type_message = "trace_ignore_status_codes must be a list of integers or pairs of integers." try: low, high = target - if not isinstance(low, int) or not isinstance(high, int): + if ( + isinstance(code, int) + and not isinstance(low, int) + or not isinstance(high, int) + ): logger.warning(wrong_type_message) continue + elif not isinstance(code, int): + logger.warning( + f"Invalid type for http.request.status_code; is {code!r} of type {type(code)}, expected an int." + ) + continue if low <= code <= high: return True except Exception: + if not isinstance(code, int): + logger.warning( + f"Invalid type for http.request.status_code; is {code!r} of type {type(code)}, expected an int." + ) + continue + logger.warning(wrong_type_message) return False From bcaa51f524becddf81a8a58f660d377772c2a6ba Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Sep 2025 16:17:08 +0200 Subject: [PATCH 08/19] add brackets to condition --- sentry_sdk/tracing.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 929aa51e89..57de1ce7c2 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -982,10 +982,8 @@ def _in_http_status_code_range(self, code, code_ranges): wrong_type_message = "trace_ignore_status_codes must be a list of integers or pairs of integers." try: low, high = target - if ( - isinstance(code, int) - and not isinstance(low, int) - or not isinstance(high, int) + if isinstance(code, int) and ( + not isinstance(low, int) or not isinstance(high, int) ): logger.warning(wrong_type_message) continue From 1423433312afe0640eb2a264c50cd992065cb3ab Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Sep 2025 16:18:41 +0200 Subject: [PATCH 09/19] . --- sentry_sdk/tracing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 57de1ce7c2..6e03b2c00f 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -989,7 +989,7 @@ def _in_http_status_code_range(self, code, code_ranges): continue elif not isinstance(code, int): logger.warning( - f"Invalid type for http.request.status_code; is {code!r} of type {type(code)}, expected an int." + f"Invalid type for http.response.status_code; is {code!r} of type {type(code)}, expected an int." ) continue @@ -999,7 +999,7 @@ def _in_http_status_code_range(self, code, code_ranges): except Exception: if not isinstance(code, int): logger.warning( - f"Invalid type for http.request.status_code; is {code!r} of type {type(code)}, expected an int." + f"Invalid type for http.response.status_code; is {code!r} of type {type(code)}, expected an int." ) continue From d62b43878f78462b9943452cde9fbd1410713c79 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Sep 2025 16:48:57 +0200 Subject: [PATCH 10/19] add another test --- tests/tracing/test_ignore_status_codes.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/tracing/test_ignore_status_codes.py b/tests/tracing/test_ignore_status_codes.py index b188d526ef..6660627a78 100644 --- a/tests/tracing/test_ignore_status_codes.py +++ b/tests/tracing/test_ignore_status_codes.py @@ -118,3 +118,19 @@ def test_malformed_argument_ignored(sentry_init, capture_events): span_or_tx.set_data("http.response.status_code", 404) assert len(events) == 1 + + +def test_transaction_not_ignored_when_status_code_has_invalid_type( + sentry_init, capture_events +): + sentry_init( + traces_sample_rate=1.0, + trace_ignore_status_codes=(404,), + ) + events = capture_events() + + with start_transaction(op="http", name="GET /"): + span_or_tx = sentry_sdk.get_current_span() + span_or_tx.set_data("http.response.status_code", "404") + + assert len(events) == 1 From 3492eea06ef89edbe87a61227fe18c56d52fcfe1 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Sep 2025 17:07:17 +0200 Subject: [PATCH 11/19] . --- tests/tracing/test_ignore_status_codes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tracing/test_ignore_status_codes.py b/tests/tracing/test_ignore_status_codes.py index 6660627a78..2253975707 100644 --- a/tests/tracing/test_ignore_status_codes.py +++ b/tests/tracing/test_ignore_status_codes.py @@ -125,7 +125,7 @@ def test_transaction_not_ignored_when_status_code_has_invalid_type( ): sentry_init( traces_sample_rate=1.0, - trace_ignore_status_codes=(404,), + trace_ignore_status_codes=((401, 404),), ) events = capture_events() From 5374552625342eff77ea8e614da24557ed384d1c Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Sep 2025 17:07:31 +0200 Subject: [PATCH 12/19] . --- sentry_sdk/tracing.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 6e03b2c00f..0a817601cc 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -982,12 +982,11 @@ def _in_http_status_code_range(self, code, code_ranges): wrong_type_message = "trace_ignore_status_codes must be a list of integers or pairs of integers." try: low, high = target - if isinstance(code, int) and ( - not isinstance(low, int) or not isinstance(high, int) - ): + if not isinstance(low, int) or not isinstance(high, int): logger.warning(wrong_type_message) continue - elif not isinstance(code, int): + + if not isinstance(code, int): logger.warning( f"Invalid type for http.response.status_code; is {code!r} of type {type(code)}, expected an int." ) @@ -997,12 +996,6 @@ def _in_http_status_code_range(self, code, code_ranges): return True except Exception: - if not isinstance(code, int): - logger.warning( - f"Invalid type for http.response.status_code; is {code!r} of type {type(code)}, expected an int." - ) - continue - logger.warning(wrong_type_message) return False From 74cde814ca64f7545cebf60ef8645d93039c8a16 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Sep 2025 17:17:05 +0200 Subject: [PATCH 13/19] . --- sentry_sdk/tracing.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 0a817601cc..cbaad37f20 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -986,12 +986,6 @@ def _in_http_status_code_range(self, code, code_ranges): logger.warning(wrong_type_message) continue - if not isinstance(code, int): - logger.warning( - f"Invalid type for http.response.status_code; is {code!r} of type {type(code)}, expected an int." - ) - continue - if low <= code <= high: return True @@ -1075,8 +1069,13 @@ def finish( super().finish(scope, end_timestamp) - if SPANDATA.HTTP_STATUS_CODE in self._data and self._in_http_status_code_range( - self._data[SPANDATA.HTTP_STATUS_CODE], + status_code = self._data.get(SPANDATA.HTTP_STATUS_CODE) + if status_code and not isinstance(status_code, int): + logger.warning( + f"Invalid type for http.response.status_code; is {status_code!r} of type {type(status_code)}, expected an int." + ) + elif status_code and self._in_http_status_code_range( + status_code, client.options["trace_ignore_status_codes"], ): logger.debug( From e851dfa8c86c465a4dee81e0b15326b9b8147440 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Sep 2025 17:29:29 +0200 Subject: [PATCH 14/19] check not none --- sentry_sdk/tracing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index cbaad37f20..492a5c1835 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -1070,11 +1070,11 @@ def finish( super().finish(scope, end_timestamp) status_code = self._data.get(SPANDATA.HTTP_STATUS_CODE) - if status_code and not isinstance(status_code, int): + if status_code is not None and not isinstance(status_code, int): logger.warning( f"Invalid type for http.response.status_code; is {status_code!r} of type {type(status_code)}, expected an int." ) - elif status_code and self._in_http_status_code_range( + elif status_code is not None and self._in_http_status_code_range( status_code, client.options["trace_ignore_status_codes"], ): From 84ef52a69020d18f32bcc85a7a029f8c97edc770 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 1 Oct 2025 10:40:19 +0200 Subject: [PATCH 15/19] change option type to set --- sentry_sdk/consts.py | 3 ++- sentry_sdk/tracing.py | 31 ++++--------------------------- 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 1507fd3912..68f046bd90 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -40,6 +40,7 @@ class CompressionAlgo(Enum): from typing import Any from typing import Sequence from typing import Tuple + from typing import AbstractSet from typing_extensions import Literal from typing_extensions import TypedDict @@ -919,7 +920,7 @@ def __init__( max_stack_frames=DEFAULT_MAX_STACK_FRAMES, # type: Optional[int] enable_logs=False, # type: bool before_send_log=None, # type: Optional[Callable[[Log, Hint], Optional[Log]]] - trace_ignore_status_codes=[], # type: Sequence[Union[int, Tuple[int, int]]] + trace_ignore_status_codes=frozenset(), # type: AbstractSet[int] ): # type: (...) -> None """Initialize the Sentry SDK with the given parameters. All parameters described here can be used in a call to `sentry_sdk.init()`. diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 492a5c1835..9dc796e5bc 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -30,7 +30,7 @@ from typing import Tuple from typing import Union from typing import TypeVar - from typing import Sequence + from typing import Set from typing_extensions import TypedDict, Unpack @@ -971,29 +971,6 @@ def _get_scope_from_finish_args( return scope_or_hub - def _in_http_status_code_range(self, code, code_ranges): - # type: (int, Sequence[Union[int, Tuple[int, int]]]) -> bool - for target in code_ranges: - if isinstance(target, int): - if code == target: - return True - continue - - wrong_type_message = "trace_ignore_status_codes must be a list of integers or pairs of integers." - try: - low, high = target - if not isinstance(low, int) or not isinstance(high, int): - logger.warning(wrong_type_message) - continue - - if low <= code <= high: - return True - - except Exception: - logger.warning(wrong_type_message) - - return False - def _get_log_representation(self): # type: () -> str return "{op}transaction <{name}>".format( @@ -1074,9 +1051,9 @@ def finish( logger.warning( f"Invalid type for http.response.status_code; is {status_code!r} of type {type(status_code)}, expected an int." ) - elif status_code is not None and self._in_http_status_code_range( - status_code, - client.options["trace_ignore_status_codes"], + elif ( + status_code is not None + and status_code in client.options["trace_ignore_status_codes"] ): logger.debug( "[Tracing] Discarding {transaction_description} because the HTTP status code {status_code} is matched by trace_ignore_status_codes: {trace_ignore_status_codes}".format( From 88c84f47a66ffda00b25a96e6a6effc67a872f88 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 1 Oct 2025 10:48:33 +0200 Subject: [PATCH 16/19] update tests --- tests/tracing/test_ignore_status_codes.py | 54 +++++++---------------- 1 file changed, 15 insertions(+), 39 deletions(-) diff --git a/tests/tracing/test_ignore_status_codes.py b/tests/tracing/test_ignore_status_codes.py index 2253975707..45d4b249d9 100644 --- a/tests/tracing/test_ignore_status_codes.py +++ b/tests/tracing/test_ignore_status_codes.py @@ -21,7 +21,9 @@ def test_no_ignored_codes(sentry_init, capture_events): def test_single_code_ignored(sentry_init, capture_events, status_code): sentry_init( traces_sample_rate=1.0, - trace_ignore_status_codes=(404,), + trace_ignore_status_codes={ + 404, + }, ) events = capture_events() @@ -39,10 +41,10 @@ def test_single_code_ignored(sentry_init, capture_events, status_code): def test_range_ignored(sentry_init, capture_events, status_code): sentry_init( traces_sample_rate=1.0, - trace_ignore_status_codes=( - ( + trace_ignore_status_codes=set( + range( 305, - 399, + 400, ), ), ) @@ -62,19 +64,19 @@ def test_range_ignored(sentry_init, capture_events, status_code): def test_variety_ignored(sentry_init, capture_events, status_code): sentry_init( traces_sample_rate=1.0, - trace_ignore_status_codes=( + trace_ignore_status_codes={ 301, 302, 303, - ( + *range( 305, - 399, + 400, ), - ( + *range( 401, - 404, + 405, ), - ), + }, ) events = capture_events() @@ -92,40 +94,14 @@ def test_variety_ignored(sentry_init, capture_events, status_code): assert len(events) == 1 -def test_malformed_argument_ignored(sentry_init, capture_events): - sentry_init( - traces_sample_rate=1.0, - trace_ignore_status_codes=( - 404.0, - "404", - "401-404", - (404,), - ( - "401", - "404", - ), - ( - 401, - 404, - 500, - ), - ), - ) - events = capture_events() - - with start_transaction(op="http", name="GET /"): - span_or_tx = sentry_sdk.get_current_span() - span_or_tx.set_data("http.response.status_code", 404) - - assert len(events) == 1 - - def test_transaction_not_ignored_when_status_code_has_invalid_type( sentry_init, capture_events ): sentry_init( traces_sample_rate=1.0, - trace_ignore_status_codes=((401, 404),), + trace_ignore_status_codes=set( + range(401, 404), + ), ) events = capture_events() From 93952028ef4a0a6ee13323cd4f9cb04aad063d80 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 1 Oct 2025 11:17:43 +0200 Subject: [PATCH 17/19] record lost events --- sentry_sdk/tracing.py | 10 +++++++++ tests/tracing/test_ignore_status_codes.py | 27 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 9dc796e5bc..4945bc6267 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -1064,6 +1064,16 @@ def finish( ], ) ) + if client.transport: + client.transport.record_lost_event( + "event_processor", data_category="transaction" + ) + + num_spans = len(self._span_recorder.spans) + 1 + client.transport.record_lost_event( + "event_processor", data_category="span", quantity=num_spans + ) + self.sampled = False if not self.sampled: diff --git a/tests/tracing/test_ignore_status_codes.py b/tests/tracing/test_ignore_status_codes.py index 45d4b249d9..b2899e0ad9 100644 --- a/tests/tracing/test_ignore_status_codes.py +++ b/tests/tracing/test_ignore_status_codes.py @@ -3,6 +3,8 @@ import pytest +from collections import Counter + def test_no_ignored_codes(sentry_init, capture_events): sentry_init( @@ -110,3 +112,28 @@ def test_transaction_not_ignored_when_status_code_has_invalid_type( span_or_tx.set_data("http.response.status_code", "404") assert len(events) == 1 + + +def test_records_lost_events(sentry_init, capture_record_lost_event_calls): + sentry_init( + traces_sample_rate=1.0, + trace_ignore_status_codes={ + 404, + }, + ) + record_lost_event_calls = capture_record_lost_event_calls() + + with start_transaction(op="http", name="GET /"): + span_or_tx = sentry_sdk.get_current_span() + span_or_tx.set_data("http.response.status_code", 404) + + with start_span(op="child-span"): + with start_span(op="child-child-span"): + pass + + assert Counter(record_lost_event_calls) == Counter( + [ + ("event_processor", "transaction", None, 1), + ("event_processor", "span", None, 3), + ] + ) From ffb1d2de18047ec769c4d9788a305cf0ef5ffa23 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 1 Oct 2025 11:19:58 +0200 Subject: [PATCH 18/19] simplify docstring --- sentry_sdk/consts.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 68f046bd90..5bcc487037 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -1310,11 +1310,9 @@ def __init__( not be sent to Sentry. :param trace_ignore_status_codes: An optional property that disables tracing for - HTTP requests with certain response codes. + HTTP requests with certain status codes. - The option is a list, where elements are individual response codes, or inclusive - ranges of response codes. Requests are not traced if any code matches or - any provided range contains the response code. + Requests are not traced if the status code is contained in the provided set. If `trace_ignore_status_codes` is not provided, requests with any status code may be traced. From 3f5a5bbc74c486531ff26254d62fe6add4370f1e Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 1 Oct 2025 14:51:32 +0200 Subject: [PATCH 19/19] remove type check for http.response.status_code --- sentry_sdk/tracing.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 4945bc6267..1697df1f22 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -1047,11 +1047,7 @@ def finish( super().finish(scope, end_timestamp) status_code = self._data.get(SPANDATA.HTTP_STATUS_CODE) - if status_code is not None and not isinstance(status_code, int): - logger.warning( - f"Invalid type for http.response.status_code; is {status_code!r} of type {type(status_code)}, expected an int." - ) - elif ( + if ( status_code is not None and status_code in client.options["trace_ignore_status_codes"] ):