8000 fix: Push scope for each response chunk (#218) · etherscan-io/sentry-python@7b01082 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7b01082

Browse files
authored
fix: Push scope for each response chunk (getsentry#218)
* fix: Push scope for each response chunk * fix: Remove implements_iterator * fix: Just use a hub everywhere
1 parent 0674e36 commit 7b01082

File tree

6 files changed

+84
-95
lines changed

6 files changed

+84
-95
lines changed

sentry_sdk/_compat.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,6 @@ def implements_str(cls):
2121

2222
exec("def reraise(tp, value, tb=None):\n raise tp, value, tb")
2323

24-
def implements_iterator(cls):
25-
cls.next = cls.__next__
26-
del cls.__next__
27-
return cls
28-
2924

3025
else:
3126
import urllib.parse as urlparse # noqa
@@ -40,8 +35,6 @@ def implements_iterator(cls):
4035
def _identity(x):
4136
return x
4237

43-
implements_iterator = _identity
44-
4538
def implements_str(x):
4639
return x
4740

sentry_sdk/integrations/wsgi.py

Lines changed: 34 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from sentry_sdk.hub import Hub, _should_send_default_pii
44
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
5-
from sentry_sdk._compat import PY2, reraise, implements_iterator
5+
from sentry_sdk._compat import PY2, reraise
66
from sentry_sdk.integrations._wsgi_common import _filter_headers
77

88

@@ -57,21 +57,20 @@ def __init__(self, app):
5757
self.app = app
5858

5959
def __call__(self, environ, start_response):
60-
hub = Hub.current
61-
hub.push_scope()
62-
with capture_internal_exceptions():
63-
with hub.configure_scope() as scope:
64-
scope._name = "wsgi"
65-
scope.add_event_processor(_make_wsgi_event_processor(environ))
60+
hub = Hub(Hub.current)
61+
62+
with hub:
63+
with capture_internal_exceptions():
64+
with hub.configure_scope() as scope:
65+
scope._name = "wsgi"
66+
scope.add_event_processor(_make_wsgi_event_processor(environ))
6667

67-
try:
68-
rv = self.app(environ, start_response)
69-
except Exception:
70-
einfo = _capture_exception(hub)
71-
hub.pop_scope_unsafe()
72-
reraise(*einfo)
68+
try:
69+
rv = self.app(environ, start_response)
70+
except Exception:
71+
reraise(*_capture_exception(hub))
7372

74-
return _ScopePoppingResponse(hub, rv)
73+
return _ScopedResponse(hub, rv)
7574

7675

7776
def _get_environ(environ):
@@ -132,45 +131,35 @@ def _capture_exception(hub):
132131
return exc_info
133132

134133

135-
@implements_iterator
136-
class _ScopePoppingResponse(object):
137-
__slots__ = ("_response", "_iterator", "_hub", "_popped")
134+
class _ScopedResponse(object):
135+
__slots__ = ("_response", "_hub")
138136

139137
def __init__(self, hub, response):
140138
self._hub = hub
141139
self._response = response
142-
self._iterator = None
143-
self._popped = False
144140

145141
def __iter__(self):
146-
try:
147-
self._iterator = iter(self._response)
148-
except Exception:
149-
reraise(*_capture_exception(self._hub))
150-
return self
151-
152-
def __next__(self):
153-
if self._iterator is None:
154-
self.__iter__()
155-
156-
try:
157-
return next(self._iterator)
158-
except StopIteration:
159-
raise
160-
except Exception:
161-
reraise(*_capture_exception(self._hub))
142+
iterator = iter(self._response)
143+
144+
while True:
145+
with self._hub:
146+
try:
147+
chunk = next(iterator)
148+
except StopIteration:
149+
break
150+
except Exception:
151+
reraise(*_capture_exception(self._hub))
152+
153+
yield chunk
162154

163155
def close(self):
164-
if not self._popped:
165-
self._hub.pop_scope_unsafe()
166-
self._popped = True
167-
168-
try:
169-
self._response.close()
170-
except AttributeError:
171-
pass
172-
except Exception:
173-
reraise(*_capture_exception(self._hub))
156+
with self._hub:
157+
try:
158+
self._response.close()
159+
except AttributeError:
160+
pass
161+
except Exception:
162+
reraise(*_capture_exception(self._hub))
174163

175164

176165
def _make_wsgi_event_processor(environ):

tests/integrations/conftest.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@
66
def capture_exceptions(monkeypatch):
77
def inner():
88
errors = set()
9-
old_capture_event = sentry_sdk.Hub.current.capture_event
9+
old_capture_event = sentry_sdk.Hub.capture_event
1010

11-
def capture_event(event, hint=None):
11+
def capture_event(self, event, hint=None):
1212
if hint:
1313
if "exc_info" in hint:
1414
error = hint["exc_info"][1]
1515
errors.add(error)
16-
return old_capture_event(event, hint=hint)
16+
return old_capture_event(self, event, hint=hint)
1717

18-
monkeypatch.setattr(sentry_sdk.Hub.current, "capture_event", capture_event)
18+
monkeypatch.setattr(sentry_sdk.Hub, "capture_event", capture_event)
1919
return errors
2020

2121
return inner

tests/integrations/django/test_basic.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
except ImportError:
1313
from django.core.urlresolvers import reverse
1414

15-
from sentry_sdk import last_event_id, capture_message
15+
from sentry_sdk import capture_message
1616
from sentry_sdk.integrations.django import DjangoIntegration
1717

1818
from tests.integrations.django.myapp.wsgi import application
@@ -120,16 +120,17 @@ def test_custom_error_handler_request_context(sentry_init, client, capture_event
120120
}
121121

122122

123-
def test_500(sentry_init, client):
123+
def test_500(sentry_init, client, capture_events):
124124
sentry_init(integrations=[DjangoIntegration()], send_default_pii=True)
125-
old_event_id = last_event_id()
125+
events = capture_events()
126+
126127
content, status, headers = client.get("/view-exc")
127128
assert status.lower() == "500 internal server error"
128129
content = b"".join(content).decode("utf-8")
129-
event_id = last_event_id()
130+
131+
event, = events
132+
event_id = event["event_id"]
130133
assert content == "Sentry error: %s" % event_id
131-
assert event_id is not None
132-
assert old_event_id != event_id
133134

134135

135136
def test_management_command_raises():

tests/integrations/flask/test_flask.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@
55

66
flask = pytest.importorskip("flask")
77

8-
from flask import Flask, request, abort
8+
from flask import Flask, Response, request, abort, stream_with_context
99

1010
from flask_login import LoginManager, login_user
1111

12-
from sentry_sdk import capture_message, capture_exception, last_event_id
12+
from sentry_sdk import (
13+
configure_scope,
14+
capture_message,
15+
capture_exception,
16+
last_event_id,
17+
)
1318
from sentry_sdk.integrations.logging import LoggingIntegration
1419
import sentry_sdk.integrations.flask as flask_sentry
1520

@@ -71,7 +76,7 @@ def test_transaction_style(
7176
@pytest.mark.parametrize("debug", (True, False))
7277
@pytest.mark.parametrize("testing", (True, False))
7378
def test_errors(sentry_init, capture_exceptions, capture_events, app, debug, testing):
74-
sentry_init(integrations=[flask_sentry.FlaskIntegration()])
79+
sentry_init(integrations=[flask_sentry.FlaskIntegration()], debug=True)
7580

7681
app.debug = debug
7782
app.testing = testing
@@ -460,3 +465,33 @@ def index():
460465
client.get("/")
461466

462467
assert not events
468+
469+
470+
def test_does_not_leak_scope(sentry_init, capture_events, app):
471+
sentry_init(integrations=[flask_sentry.FlaskIntegration()])
472+
events = capture_events()
473+
474+
with configu F438 re_scope() as scope:
475+
scope.set_tag("request_data", False)
476+
477+
@app.route("/")
478+
def index():
479+
with configure_scope() as scope:
480+
scope.set_tag("request_data", True)
481+
482+
def generate():
483+
for row in range(1000):
484+
with configure_scope() as scope:
485+
assert scope._tags["request_data"]
486+
487+
yield str(row) + "\n"
488+
489+
return Response(stream_with_context(generate()), mimetype="text/csv")
490+
491+
client = app.test_client()
492+
response = client.get("/")
493+
assert response.data.decode() == "".join(str(row) + "\n" for row in range(1000))
494+
assert not events
495+
496+
with configure_scope() as scope:
497+
assert not scope._tags["request_data"]

tests/integrations/wsgi/test_wsgi.py

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
from werkzeug.test import Client
22
import pytest
33

4-
from sentry_sdk import Hub
5-
6-
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware, _ScopePoppingResponse
4+
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
75

86

97
@pytest.fixture
@@ -32,30 +30,3 @@ def test_basic(sentry_init, crashing_app, capture_events):
3230
"query_string": "",
3331
"url": "http://localhost/",
3432
}
35-
36-
37-
def test_calls_close():
38-
hub = Hub.current
39-
stack_size = len(hub._stack)
40-
closes = []
41-
42-
hub.push_scope()
43-
44-
class Foo(object):
45-
def __iter__(self):
46-
yield 1
47-
yield 2
48-
yield 3
49-
50-
def close(self):
51-
closes.append(1)
52-
53-
response = _ScopePoppingResponse(hub, Foo())
54-
list(response)
55-
response.close()
56-
response.close()
57-
response.close()
58-
59-
# multiple close calls are just forwarded, but the scope is only popped once
60-
assert len(closes) == 3
61-
assert len(hub._stack) == stack_size

0 commit comments

Comments
 (0)
0