|
25 | 25 | from sentry_sdk.utils import logger
|
26 | 26 | from sentry_sdk.serializer import MAX_DATABAG_BREADTH
|
27 | 27 | from sentry_sdk.consts import DEFAULT_MAX_BREADCRUMBS, DEFAULT_MAX_VALUE_LENGTH
|
| 28 | +from sentry_sdk._types import TYPE_CHECKING |
| 29 | + |
| 30 | +if TYPE_CHECKING: |
| 31 | + from collections.abc import Callable |
| 32 | + from typing import Any, Optional, Union |
| 33 | + from sentry_sdk._types import Event |
28 | 34 |
|
29 | 35 | try:
|
30 | 36 | from unittest import mock # python 3.3 and above
|
@@ -1196,3 +1202,114 @@ def test_debug_option(
|
1196 | 1202 | assert "something is wrong" in caplog.text
|
1197 | 1203 | else:
|
1198 | 1204 | assert "something is wrong" not in caplog.text
|
| 1205 | + |
| 1206 | + |
| 1207 | +class IssuesSamplerTestConfig: |
| 1208 | + def __init__( |
| 1209 | + self, |
| 1210 | + expected_events, |
| 1211 | + sampler_function=None, |
| 1212 | + sample_rate=None, |
| 1213 | + exception_to_raise=Exception, |
| 1214 | + ): |
| 1215 | + # type: (int, Optional[Callable[[Event], Union[float, bool]]], Optional[float], type[Exception]) -> None |
| 1216 | + self.sampler_function_mock = ( |
| 1217 | + None |
| 1218 | + if sampler_function is None |
| 1219 | + else mock.MagicMock(side_effect=sampler_function) |
| 1220 | + ) |
| 1221 | + self.expected_events = expected_events |
| 1222 | + self.sample_rate = sample_rate |
| 1223 | + self.exception_to_raise = exception_to_raise |
| 1224 | + |
| 1225 | + def init_sdk(self, sentry_init): |
| 1226 | + # type: (Callable[[*Any], None]) -> None |
| 1227 | + sentry_init( |
| 1228 | + error_sampler=self.sampler_function_mock, sample_rate=self.sample_rate |
| 1229 | + ) |
| 1230 | + |
| 1231 | + def raise_exception(self): |
| 1232 | + # type: () -> None |
| 1233 | + raise self.exception_to_raise() |
| 1234 | + |
| 1235 | + |
| 1236 | +@mock.patch("sentry_sdk.client.random.random", return_value=0.618) |
| 1237 | +@pytest.mark.parametrize( |
| 1238 | + "test_config", |
| 1239 | + ( |
| 1240 | + # Baseline test with error_sampler only, both floats and bools |
| 1241 | + IssuesSamplerTestConfig(sampler_function=lambda *_: 1.0, expected_events=1), |
| 1242 | + IssuesSamplerTestConfig(sampler_function=lambda *_: 0.7, expected_events=1), |
| 1243 | + IssuesSamplerTestConfig(sampler_function=lambda *_: 0.6, expected_events=0), |
| 1244 | + IssuesSamplerTestConfig(sampler_function=lambda *_: 0.0, expected_events=0), |
| 1245 | + IssuesSamplerTestConfig(sampler_function=lambda *_: True, expected_events=1), |
| 1246 | + IssuesSamplerTestConfig(sampler_function=lambda *_: False, expected_events=0), |
| 1247 | + # Baseline test with sample_rate only |
| 1248 | + IssuesSamplerTestConfig(sample_rate=1.0, expected_events=1), |
| 1249 | + IssuesSamplerTestConfig(sample_rate=0.7, expected_events=1), |
| 1250 | + IssuesSamplerTestConfig(sample_rate=0.6, expected_events=0), |
| 1251 | + IssuesSamplerTestConfig(sample_rate=0.0, expected_events=0), |
| 1252 | + # error_sampler takes precedence over sample_rate |
| 1253 | + IssuesSamplerTestConfig( |
| 1254 | + sampler_function=lambda *_: 1.0, sample_rate=0.0, expected_events=1 |
| 1255 | + ), |
| 1256 | + IssuesSamplerTestConfig( |
| 1257 | + sampler_function=lambda *_: 0.0, sample_rate=1.0, expected_events=0 |
| 1258 | + ), |
| 1259 | + # Different sample rates based on exception, retrieved both from event and hint |
| 1260 | + IssuesSamplerTestConfig( |
| 1261 | + sampler_function=lambda event, _: { |
| 1262 | + "ZeroDivisionError": 1.0, |
| 1263 | + "AttributeError": 0.0, |
| 1264 | + }[event["exception"]["values"][0]["type"]], |
| 1265 | + exception_to_raise=ZeroDivisionError, |
| 1266 | + expected_events=1, |
| 1267 | + ), |
| 1268 | + IssuesSamplerTestConfig( |
| 1269 | + sampler_function=lambda event, _: { |
| 1270 | + "ZeroDivisionError": 1.0, |
| 1271 | + "AttributeError": 0.0, |
| 1272 | + }[event["exception"]["values"][0]["type"]], |
| 1273 | + exception_to_raise=AttributeError, |
| 1274 | + expected_events=0, |
| 1275 | + ), |
| 1276 | + IssuesSamplerTestConfig( |
| 1277 | + sampler_function=lambda _, hint: { |
| 1278 | + ZeroDivisionError: 1.0, |
| 1279 | + AttributeError: 0.0, |
| 1280 | + }[hint["exc_info"][0]], |
| 1281 | + exception_to_raise=ZeroDivisionError, |
| 1282 | + expected_events=1, |
| 1283 | + ), |
| 1284 | + IssuesSamplerTestConfig( |
| 1285 | + sampler_function=lambda _, hint: { |
| 1286 | + ZeroDivisionError: 1.0, |
| 1287 | + AttributeError: 0.0, |
| 1288 | + }[hint["exc_info"][0]], |
| 1289 | + exception_to_raise=AttributeError, |
| 1290 | + expected_events=0, |
| 1291 | + ), |
| 1292 | + # If sampler returns invalid value, we should still send the event |
| 1293 | + IssuesSamplerTestConfig( |
| 1294 | + sampler_function=lambda *_: "This is an invalid return value for the sampler", |
| 1295 | + expected_events=1, |
| 1296 | + ), |
| 1297 | + ), |
| 1298 | +) |
| 1299 | +def test_error_sampler(_, sentry_init, capture_events, test_config): |
| 1300 | + test_config.init_sdk(sentry_init) |
| 1301 | + |
| 1302 | + events = capture_events() |
| 1303 | + |
| 1304 | + try: |
| 1305 | + test_config.raise_exception() |
| 1306 | + except Exception: |
| 1307 | + capture_exception() |
| 1308 | + |
| 1309 | + assert len(events) == test_config.expected_events |
| 1310 | + |
| 1311 | + if test_config.sampler_function_mock is not None: |
| 1312 | + assert test_config.sampler_function_mock.call_count == 1 |
| 1313 | + |
| 1314 | + # Ensure two arguments (the event and hint) were passed to the sampler function |
| 1315 | + assert len(test_config.sampler_function_mock.call_args[0]) == 2 |
0 commit comments