Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
11 changes: 11 additions & 0 deletions sentry_sdk/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()`.
Expand Down Expand Up @@ -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
Expand Down
54 changes: 51 additions & 3 deletions sentry_sdk/tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -970,6 +971,35 @@ 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(
op=("<" + self.op + "> " if self.op else ""), name=self.name
)

def finish(
self,
scope=None, # type: Optional[sentry_sdk.Scope]
Expand Down Expand Up @@ -1039,6 +1069,26 @@ 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 status_code is not None and self._in_http_status_code_range(
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_representation(),
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:
# At this point a `sampled = None` should have already been resolved
# to a concrete decision.
Expand Down Expand Up @@ -1186,9 +1236,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):
Expand Down
136 changes: 136 additions & 0 deletions tests/tracing/test_ignore_status_codes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
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


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),),
)
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