Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(wsgi): Log SystemExit (with non-zero exit code) and KeyboardInterrupt #380

Merged
merged 9 commits into from
Jun 1, 2019
35 changes: 20 additions & 15 deletions sentry_sdk/integrations/wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ def __call__(self, environ, start_response):

try:
rv = self.app(environ, start_response)
except Exception:
reraise(*_capture_exception(hub))
except BaseException as e:
reraise(*_capture_exception(hub, e))

return _ScopedResponse(hub, rv)

Expand Down Expand Up @@ -149,17 +149,22 @@ def get_client_ip(environ):
return environ.get("REMOTE_ADDR")


def _capture_exception(hub):
# type: (Hub) -> ExcInfo
def _capture_exception(hub, e):
untitaker marked this conversation as resolved.
Show resolved Hide resolved
# type: (Hub, BaseException) -> 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)
# 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


Expand All @@ -181,8 +186,8 @@ def __iter__(self):
chunk = next(iterator)
except StopIteration:
break
except Exception:
reraise(*_capture_exception(self._hub))
except BaseException as e:
reraise(*_capture_exception(self._hub, e))

yield chunk

Expand All @@ -192,8 +197,8 @@ def close(self):
self._response.close()
except AttributeError:
pass
except Exception:
reraise(*_capture_exception(self._hub))
except BaseException as e:
reraise(*_capture_exception(self._hub, e))


def _make_wsgi_event_processor(environ):
Expand Down
77 changes: 77 additions & 0 deletions tests/integrations/wsgi/test_wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"