Skip to content

Commit 98399ca

Browse files
committed
fix: Capture user info at right moment
1 parent d794c3a commit 98399ca

File tree

4 files changed

+72
-76
lines changed

4 files changed

+72
-76
lines changed

sentry_sdk/integrations/_wsgi.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ def event_processor(event):
208208
# if the code below fails halfway through we at least have some data
209209
request_info = event.setdefault("request", {})
210210

211+
if _should_send_default_pii():
212+
user_info = event.setdefault("user", {})
213+
user_info["ip_address"] = get_client_ip(environ)
214+
211215
if "query_string" not in request_info:
212216
request_info["query_string"] = environ.get("QUERY_STRING")
213217

sentry_sdk/integrations/django.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from sentry_sdk import get_current_hub, capture_exception
1414
from sentry_sdk.hub import _internal_exceptions, _should_send_default_pii
15-
from ._wsgi import RequestExtractor, get_client_ip, run_wsgi_app
15+
from ._wsgi import RequestExtractor, run_wsgi_app
1616
from . import Integration
1717

1818

@@ -139,10 +139,7 @@ def size_of_file(self, file):
139139

140140

141141
def _set_user_info(request, event):
142-
if "user" in event:
143-
return
144-
145-
event["user"] = user_info = {"ip_address": get_client_ip(request.META)}
142+
user_info = event.setdefault("user", {})
146143

147144
user = getattr(request, "user", None)
148145

sentry_sdk/integrations/flask.py

Lines changed: 64 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@
88
from . import Integration
99

1010
try:
11-
from flask_login import current_user as current_user_proxy
11+
import flask_login
1212
except ImportError:
13-
current_user_proxy = None
13+
flask_login = None
1414

1515
from flask import Flask, _request_ctx_stack, _app_ctx_stack
1616
from flask.signals import (
1717
appcontext_pushed,
1818
appcontext_tearing_down,
1919
got_request_exception,
20+
request_started,
2021
)
2122

2223

@@ -29,6 +30,7 @@ def __init__(self):
2930
def install(self, client):
3031
appcontext_pushed.connect(_push_appctx)
3132
appcontext_tearing_down.connect(_pop_appctx)
33+
request_started.connect(_request_started)
3234
got_request_exception.connect(_capture_exception)
3335

3436
old_app = Flask.__call__
@@ -45,61 +47,25 @@ def _push_appctx(*args, **kwargs):
4547
# always want to push scope regardless of whether WSGI app might already
4648
# have (not the case for CLI for example)
4749
get_current_hub().push_scope()
50+
get_current_hub().add_event_processor(_make_user_event_processor)
4851

49-
app = getattr(_app_ctx_stack.top, "app", None)
5052

51-
try:
52-
weak_request = weakref.ref(_request_ctx_stack.top.request)
53-
except AttributeError:
54-
weak_request = lambda: None
53+
def _pop_appctx(*args, **kwargs):
54+
get_current_hub().pop_scope_unsafe()
5555

56-
try:
57-
weak_user = weakref.ref(current_user_proxy._get_current_object())
58-
except (AttributeError, RuntimeError, TypeError):
59-
weak_user = lambda: None
6056

57+
def _request_started(sender, **kwargs):
58+
weak_request = weakref.ref(_request_ctx_stack.top.request)
59+
app = _app_ctx_stack.top.app
6160
get_current_hub().add_event_processor(
62-
lambda: _make_event_processor(app, weak_request, weak_user)
61+
lambda: _make_request_event_processor(app, weak_request)
6362
)
6463

6564

66-
def _pop_appctx(*args, **kwargs):
67-
get_current_hub().pop_scope_unsafe()
68-
69-
7065
def _capture_exception(sender, exception, **kwargs):
7166
capture_exception(exception)
7267

7368

74-
def _make_event_processor(app, weak_request, weak_user):
75-
client_options = get_current_hub().client.options
76-
77-
def event_processor(event):
78-
request = weak_request()
79-
80-
# if the request is gone we are fine not logging the data from
81-
# it. This might happen if the processor is pushed away to
82-
# another thread.
83-
if request is not None:
84-
if "transaction" not in event:
85-
try:
86-
event["transaction"] = request.url_rule.endpoint
87-
except Exception:
88-
pass
89-
90-
with _internal_exceptions():
91-
FlaskRequestExtractor(request).extract_into_event(event, client_options)
92-
93-
if _should_send_default_pii():
94-
with _internal_exceptions():
95-
_set_user_info(request, weak_user(), event)
96-
97-
with _internal_exceptions():
98-
_process_frames(app, event)
99-
100-
return event_processor
101-
102-
10369
def _process_frames(app, event):
10470
for frame in event.iter_frames():
10571
if "in_app" in frame:
@@ -139,28 +105,57 @@ def size_of_file(self, file):
139105
return file.content_length
140106

141107

142-
def _set_user_info(request, user, event):
143-
if "user" in event:
144-
return
145-
146-
if request:
147-
try:
148-
ip_address = request.access_route[0]
149-
except IndexError:
150-
ip_address = request.remote_addr
151-
else:
152-
ip_address = None
153-
154-
user_info = {"id": None, "ip_address": ip_address}
155-
156-
try:
157-
user_info["id"] = user.get_id()
158-
# TODO: more configurable user attrs here
159-
except AttributeError:
160-
# might happen if:
161-
# - flask_login could not be imported
162-
# - flask_login is not configured
163-
# - no user is logged in
164-
pass
108+
def _make_request_event_processor(app, weak_request):
109+
client_options = get_current_hub().client.options
110+
111+
def inner(event):
112+
with _internal_exceptions():
113+
_process_frames(app, event)
114+
115+
request = weak_request()
116+
117+
# if the request is gone we are fine not logging the data from
118+
# it. This might happen if the processor is pushed away to
119+
# another thread.
120+
if request is None:
121+
return
122+
123+
if "transaction" not in event:
124+
try:
125+
event["transaction"] = request.url_rule.endpoint
126+
except Exception:
127+
pass
128+
129+
with _internal_exceptions():
130+
FlaskRequestExtractor(request).extract_into_event(event, client_options)
131+
132+
return inner
133+
134+
135+
def _make_user_event_processor():
136+
def inner(event):
137+
if flask_login is None or not _should_send_default_pii():
138+
return
139+
140+
user = flask_login.current_user
141+
if user is None:
142+
return
143+
144+
with _internal_exceptions():
145+
# Access this object as late as possible as accessing the user
146+
# is relatively costly
147+
148+
user_info = event.setdefault("user", {})
149+
150+
if user_info.get("id", None) is None:
151+
try:
152+
user_info["id"] = user.get_id()
153+
# TODO: more configurable user attrs here
154+
except AttributeError:
155+
# might happen if:
156+
# - flask_login could not be imported
157+
# - flask_login is not configured
158+
# - no user is logged in
159+
pass
165160

166-
event["user"] = user_info
161+
return inner

tests/integrations/flask/test_flask.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def index():
7474
def test_flask_login_not_installed(sentry_init, app, capture_events, monkeypatch):
7575
sentry_init(integrations=[flask_sentry.FlaskIntegration()])
7676

77-
monkeypatch.setattr(flask_sentry, "current_user", None)
77+
monkeypatch.setattr(flask_sentry, "flask_login", None)
7878

7979
events = capture_events()
8080

@@ -88,7 +88,7 @@ def test_flask_login_not_installed(sentry_init, app, capture_events, monkeypatch
8888
def test_flask_login_not_configured(sentry_init, app, capture_events, monkeypatch):
8989
sentry_init(integrations=[flask_sentry.FlaskIntegration()])
9090

91-
assert flask_sentry.current_user is not None
91+
assert flask_sentry.flask_login
9292

9393
events = capture_events()
9494
client = app.test_client()

0 commit comments

Comments
 (0)