Skip to content

Commit ddab2d4

Browse files
saifelseuntitaker
authored andcommitted
fix(wsgi): Log SystemExit (with non-zero exit code) and KeyboardInterrupt (getsentry#380)
* Log SystemExit (with non-zero exit code) and KeyboardInterrupt in WSGI middleware Neither SystemExit nor KeyboardInterrupt subclass Exception, so we must explicitly handle these two exception classes in addition to Exception in the WSGI middleware. This is notably a direct port from raven-python: - https://github.com/getsentry/raven-python/blob/master/raven/middleware.py - https://github.com/getsentry/raven-python/blob/master/tests/middleware/tests.py Fixes: getsentryGH-379
1 parent 3fc2a2f commit ddab2d4

File tree

2 files changed

+94
-10
lines changed

2 files changed

+94
-10
lines changed

sentry_sdk/integrations/wsgi.py

+17-10
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def __call__(self, environ, start_response):
8787

8888
try:
8989
rv = self.app(environ, start_response)
90-
except Exception:
90+
except BaseException:
9191
reraise(*_capture_exception(hub))
9292

9393
return _ScopedResponse(hub, rv)
@@ -151,15 +151,22 @@ def get_client_ip(environ):
151151

152152
def _capture_exception(hub):
153153
# type: (Hub) -> ExcInfo
154+
exc_info = sys.exc_info()
155+
154156
# Check client here as it might have been unset while streaming response
155157
if hub.client is not None:
156-
exc_info = sys.exc_info()
157-
event, hint = event_from_exception(
158-
exc_info,
159-
client_options=hub.client.options,
160-
mechanism={"type": "wsgi", "handled": False},
161-
)
162-
hub.capture_event(event, hint=hint)
158+
e = exc_info[1]
159+
160+
# SystemExit(0) is the only uncaught exception that is expected behavior
161+
should_skip_capture = isinstance(e, SystemExit) and e.code in (0, None)
162+
if not should_skip_capture:
163+
event, hint = event_from_exception(
164+
exc_info,
165+
client_options=hub.client.options,
166+
mechanism={"type": "wsgi", "handled": False},
167+
)
168+
hub.capture_event(event, hint=hint)
169+
163170
return exc_info
164171

165172

@@ -181,7 +188,7 @@ def __iter__(self):
181188
chunk = next(iterator)
182189
except StopIteration:
183190
break
184-
except Exception:
191+
except BaseException:
185192
reraise(*_capture_exception(self._hub))
186193

187194
yield chunk
@@ -192,7 +199,7 @@ def close(self):
192199
self._response.close()
193200
except AttributeError:
194201
pass
195-
except Exception:
202+
except BaseException:
196203
reraise(*_capture_exception(self._hub))
197204

198205

tests/integrations/wsgi/test_wsgi.py

+77
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,28 @@ def app(environ, start_response):
1212
return app
1313

1414

15+
class IterableApp(object):
16+
def __init__(self, iterable):
17+
self.iterable = iterable
18+
19+
def __call__(self, environ, start_response):
20+
return self.iterable
21+
22+
23+
class ExitingIterable(object):
24+
def __init__(self, exc_func):
25+
self._exc_func = exc_func
26+
27+
def __iter__(self):
28+
return self
29+
30+
def __next__(self):
31+
raise self._exc_func()
32+
33+
def next(self):
34+
return type(self).__next__(self)
35+
36+
1537
def test_basic(sentry_init, crashing_app, capture_events):
1638
sentry_init(send_default_pii=True)
1739
app = SentryWsgiMiddleware(crashing_app)
@@ -30,3 +52,58 @@ def test_basic(sentry_init, crashing_app, capture_events):
3052
"query_string": "",
3153
"url": "http://localhost/",
3254
}
55+
56+
57+
@pytest.fixture(params=[0, None])
58+
def test_systemexit_zero_is_ignored(sentry_init, capture_events, request):
59+
zero_code = request.param
60+
sentry_init(send_default_pii=True)
61+
iterable = ExitingIterable(lambda: SystemExit(zero_code))
62+
app = SentryWsgiMiddleware(IterableApp(iterable))
63+
client = Client(app)
64+
events = capture_events()
65+
66+
with pytest.raises(SystemExit):
67+
client.get("/")
68+
69+
assert len(events) == 0
70+
71+
72+
@pytest.fixture(params=["", "foo", 1, 2])
73+
def test_systemexit_nonzero_is_captured(sentry_init, capture_events, request):
74+
nonzero_code = request.param
75+
sentry_init(send_default_pii=True)
76+
iterable = ExitingIterable(lambda: SystemExit(nonzero_code))
77+
app = SentryWsgiMiddleware(IterableApp(iterable))
78+
client = Client(app)
79+
events = capture_events()
80+
81+
with pytest.raises(SystemExit):
82+
client.get("/")
83+
84+
event, = events
85+
86+
assert "exception" in event
87+
exc = event["exception"]["values"][-1]
88+
assert exc["type"] == "SystemExit"
89+
assert exc["value"] == nonzero_code
90+
assert event["level"] == "error"
91+
92+
93+
def test_keyboard_interrupt_is_captured(sentry_init, capture_events):
94+
sentry_init(send_default_pii=True)
95+
iterable = ExitingIterable(lambda: KeyboardInterrupt())
96+
app = SentryWsgiMiddleware(IterableApp(iterable))
97+
client = Client(app)
98+
events = capture_events()
99+
100+
with pytest.raises(KeyboardInterrupt):
101+
client.get("/")
102+
103+
event, = events
104+
105+
assert "exception" in event
106+
exc = event["exception"]["values"][-1]
107+
assert exc["type"] == "KeyboardInterrupt"
108+
assert exc["value"] == ""
109+
assert event["level"] == "error"

0 commit comments

Comments
 (0)