diff --git a/sentry_sdk/integrations/wsgi.py b/sentry_sdk/integrations/wsgi.py index f91f10ac67..9a098b6697 100644 --- a/sentry_sdk/integrations/wsgi.py +++ b/sentry_sdk/integrations/wsgi.py @@ -87,7 +87,7 @@ def __call__(self, environ, start_response): try: rv = self.app(environ, start_response) - except Exception: + except BaseException: reraise(*_capture_exception(hub)) return _ScopedResponse(hub, rv) @@ -151,15 +151,22 @@ def get_client_ip(environ): def _capture_exception(hub): # type: (Hub) -> ExcInfo + exc_info = sys.exc_info() + # Check client here as it might have been unset while streaming response if hub.client is not None: - exc_info = sys.exc_info() - event, hint = event_from_exception( - exc_info, - client_options=hub.client.options, - mechanism={"type": "wsgi", "handled": False}, - ) - hub.capture_event(event, hint=hint) + e = exc_info[1] + + # SystemExit(0) is the only uncaught exception that is expected behavior + should_skip_capture = isinstance(e, SystemExit) and e.code in (0, None) + if not should_skip_capture: + event, hint = event_from_exception( + exc_info, + client_options=hub.client.options, + mechanism={"type": "wsgi", "handled": False}, + ) + hub.capture_event(event, hint=hint) + return exc_info @@ -181,7 +188,7 @@ def __iter__(self): chunk = next(iterator) except StopIteration: break - except Exception: + except BaseException: reraise(*_capture_exception(self._hub)) yield chunk @@ -192,7 +199,7 @@ def close(self): self._response.close() except AttributeError: pass - except Exception: + except BaseException: reraise(*_capture_exception(self._hub)) diff --git a/tests/integrations/wsgi/test_wsgi.py b/tests/integrations/wsgi/test_wsgi.py index b63540c3f6..be26496e98 100644 --- a/tests/integrations/wsgi/test_wsgi.py +++ b/tests/integrations/wsgi/test_wsgi.py @@ -12,6 +12,28 @@ def app(environ, start_response): return app +class IterableApp(object): + def __init__(self, iterable): + self.iterable = iterable + + def __call__(self, environ, start_response): + return self.iterable + + +class ExitingIterable(object): + def __init__(self, exc_func): + self._exc_func = exc_func + + def __iter__(self): + return self + + def __next__(self): + raise self._exc_func() + + def next(self): + return type(self).__next__(self) + + def test_basic(sentry_init, crashing_app, capture_events): sentry_init(send_default_pii=True) app = SentryWsgiMiddleware(crashing_app) @@ -30,3 +52,58 @@ def test_basic(sentry_init, crashing_app, capture_events): "query_string": "", "url": "http://localhost/", } + + +@pytest.fixture(params=[0, None]) +def test_systemexit_zero_is_ignored(sentry_init, capture_events, request): + zero_code = request.param + sentry_init(send_default_pii=True) + iterable = ExitingIterable(lambda: SystemExit(zero_code)) + app = SentryWsgiMiddleware(IterableApp(iterable)) + client = Client(app) + events = capture_events() + + with pytest.raises(SystemExit): + client.get("/") + + assert len(events) == 0 + + +@pytest.fixture(params=["", "foo", 1, 2]) +def test_systemexit_nonzero_is_captured(sentry_init, capture_events, request): + nonzero_code = request.param + sentry_init(send_default_pii=True) + iterable = ExitingIterable(lambda: SystemExit(nonzero_code)) + app = SentryWsgiMiddleware(IterableApp(iterable)) + client = Client(app) + events = capture_events() + + with pytest.raises(SystemExit): + client.get("/") + + event, = events + + assert "exception" in event + exc = event["exception"]["values"][-1] + assert exc["type"] == "SystemExit" + assert exc["value"] == nonzero_code + assert event["level"] == "error" + + +def test_keyboard_interrupt_is_captured(sentry_init, capture_events): + sentry_init(send_default_pii=True) + iterable = ExitingIterable(lambda: KeyboardInterrupt()) + app = SentryWsgiMiddleware(IterableApp(iterable)) + client = Client(app) + events = capture_events() + + with pytest.raises(KeyboardInterrupt): + client.get("/") + + event, = events + + assert "exception" in event + exc = event["exception"]["values"][-1] + assert exc["type"] == "KeyboardInterrupt" + assert exc["value"] == "" + assert event["level"] == "error"