10000 Merge remote-tracking branch 'origin/master' into release/0.7.10 · pythonthings/sentry-python@5adf697 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5adf697

Browse files
committed
Merge remote-tracking branch 'origin/master' into release/0.7.10
2 parents 81b6336 + 46f8527 commit 5adf697

File tree

7 files changed

+177
-45
lines changed

7 files changed

+177
-45
lines changed

sentry_sdk/integrations/django/__init__.py

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,6 @@
2121
from django.utils.datastructures import MultiValueDict # type: ignore
2222
from typing import List
2323

24-
try:
25-
import psycopg2.sql # type: ignore
26-
27-
def sql_to_string(sql):
28-
# type: (Any) -> str
29-
if isinstance(sql, psycopg2.sql.SQL):
30-
return sql.string
31-
return sql
32-
33-
34-
except ImportError:
35-
36-
def sql_to_string(sql):
37-
# type: (Any) -> str
38-
return sql
39-
4024

4125
try:
4226
from django.urls import resolve # type: ignore
@@ -265,6 +249,12 @@ def files(self):
265249
def size_of_file(self, file):
266250
return file.size
267251

252+
def parsed_body(self):
D7AF 253+
try:
254+
return self.request.data
255+
except AttributeError:
256+
return RequestExtractor.parsed_body(self)
257+
268258

269259
def _set_user_info(request, event):
270260
# type: (WSGIRequest, Dict[str, Any]) -> None
@@ -332,21 +322,38 @@ def record_sql(sql, params, cursor=None):
332322
if hub.get_integration(DjangoIntegration) is None:
333323
return
334324

335-
with capture_internal_exceptions():
336-
if cursor and hasattr(cursor, "mogrify"): # psycopg2
337-
real_sql = cursor.mogrify(sql, params)
338-
with capture_internal_exceptions():
325+
real_sql = None
326+
real_params = None
327+
328+
try:
329+
# Prefer our own SQL formatting logic because it's the only one that
330+
# has proper value trimming.
331+
real_sql, real_params = format_sql(sql, params)
332+
if real_sql:
333+
real_sql = format_and_strip(real_sql, real_params)
334+
except Exception:
335+
pass
336+
337+
if not real_sql and cursor and hasattr(cursor, "mogrify"):
338+
# If formatting failed and we're using psycopg2, it could be that we're
339+
# looking at a query that uses Composed objects. Use psycopg2's mogrify
340+
# function to format the query. We lose per-parameter trimming but gain
341+
# accuracy in formatting.
342+
#
343+
# This is intentionally the second choice because we assume Composed
344+
# queries are not widely used, while per-parameter trimming is
345+
# generally highly desirable.
3419 346+
try:
347+
if cursor and hasattr(cursor, "mogrify"):
348+
real_sql = cursor.mogrify(sql, params)
339349
if isinstance(real_sql, bytes):
340350
real_sql = real_sql.decode(cursor.connection.encoding)
341-
else:
342-
real_sql, real_params = format_sql(sql, params)
343-
344-
if real_params:
345-
try:
346-
real_sql = format_and_strip(real_sql, real_params)
347-
except Exception:
348-
pass
349-
hub.add_breadcrumb(message=real_sql, category="query")
351+
except Exception:
352+
pass
353+
354+
if real_sql:
355+
with capture_internal_exceptions():
356+
hub.add_breadcrumb(message=real_sql, category="query")
350357

351358

352359
def install_sql_hook():

sentry_sdk/utils.py

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -579,24 +579,41 @@ def to_string(value):
579579
return repr(value)[1:-1]
580580

581581

582-
def iter_event_frames(event):
582+
def iter_event_stacktraces(event):
583583
# type: (Dict[str, Any]) -> Iterator[Dict[str, Any]]
584-
stacktraces = []
585584
if "stacktrace" in event:
586-
stacktraces.append(event["stacktrace"])
585+
yield event["stacktrace"]
587586
if "exception" in event:
588587
for exception in event["exception"].get("values") or ():
589588
if "stacktrace" in exception:
590-
stacktraces.append(exception["stacktrace"])
591-
for stacktrace in stacktraces:
589+
yield exception["stacktrace"]
590+
591+
592+
def iter_event_frames(event):
593+
# type: (Dict[str, Any]) -> Iterator[Dict[str, Any]]
594+
for stacktrace in iter_event_stacktraces(event):
592595
for frame in stacktrace.get("frames") or ():
593596
yield frame
594597

595598

596599
def handle_in_app(event, in_app_exclude=None, in_app_include=None):
597600
# type: (Dict[str, Any], List, List) -> Dict[str, Any]
601+
for stacktrace in iter_event_stacktraces(event):
602+
handle_in_app_impl(
603+
stacktrace.get("frames"),
604+
in_app_exclude=in_app_exclude,
605+
in_app_include=in_app_include,
606+
)
607+
608+
return event
609+
610+
611+
def handle_in_app_impl(frames, in_app_exclude, in_app_include):
612+
if not frames:
613+
return
614+
598615
any_in_app = False
599-
for frame in iter_event_frames(event):
616+
for frame in frames:
600617
in_app = frame.get("in_app")
601618
if in_app is not None:
602619
if in_app:
@@ -606,18 +623,18 @@ def handle_in_app(event, in_app_exclude=None, in_app_include=None):
606623
module = frame.get("module")
607624
if not module:
608625
continue
609-
610-
if _module_in_set(module, in_app_exclude):
611-
frame["in_app"] = False
612-
if _module_in_set(module, in_app_include):
626+
elif _module_in_set(module, in_app_include):
613627
frame["in_app"] = True
614628
any_in_app = True
629+
elif _module_in_set(module, in_app_exclude):
630+
frame["in_app"] = False
615631

616632
if not any_in_app:
617-
for frame in iter_event_frames(event):
618-
frame["in_app"] = True
633+
for frame in frames:
634+
if frame.get("in_app") is None:
635+
frame["in_app"] = True
619636

620-
return event
637+
return frames
621638

622639

623640
def exc_info_from_error(error):

tests/integrations/django/myapp/urls.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,13 @@
3232
path("template-exc", views.template_exc, name="template_exc"),
3333
]
3434

35+
36+
try:
37+
urlpatterns.append(
38+
path("rest-framework-exc", views.rest_framework_exc, name="rest_framework_exc")
39+
)
40+
except AttributeError:
41+
pass
42+
3543
handler500 = views.handler500
3644
handler404 = views.handler404

tests/integrations/django/myapp/views.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@
44
from django.shortcuts import render
55
from django.views.generic import ListView
66

7+
try:
8+
from rest_framework.decorators import api_view
9+
10+
@api_view(["POST"])
11+
def rest_framework_exc(request):
12+
1 / 0
13+
14+
15+
except ImportError:
16+
pass
17+
18+
719
import sentry_sdk
820

921

tests/integrations/django/test_basic.py

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
import json
23

34
from werkzeug.test import Client
45
from django.contrib.auth.models import User
@@ -225,7 +226,7 @@ def test_sql_psycopg2_string_composition(sentry_init, capture_events, query):
225226
if "postgres" not in connections:
226227
pytest.skip("postgres tests disabled")
227228

228-
import psycopg2
229+
import psycopg2.sql
229230

230231
sql = connections["postgres"].cursor()
231232

@@ -248,7 +249,7 @@ def test_sql_psycopg2_placeholders(sentry_init, capture_events):
248249
if "postgres" not in connections:
249250
pytest.skip("postgres tests disabled")
250251

251-
import psycopg2
252+
import psycopg2.sql
252253

253254
sql = connections["postgres"].cursor()
254255

@@ -389,3 +390,68 @@ def test_template_exception(sentry_init, client, capture_events):
389390
(None, None),
390391
(u"invalid_block_tag", u"django.template.base"),
391392
]
393+
394+
395+
@pytest.mark.parametrize(
396+
"type,event_request",
397+
[
398+
[
399+
"json",
400+
{
401+
"cookies": {},
402+
"data": {"foo": "bar"},
403+
"env": {"SERVER_NAME": "localhost", "SERVER_PORT": "80"},
404+
"headers": {
405+
"Content-Length": "14",
406+
"Content-Type": "application/json",
407+
"Host": "localhost",
408+
},
409+
"method": "POST",
410+
"query_string": "",
411+
"url": "http://localhost/rest-framework-exc",
412+
},
413+
],
414+
[
415+
"formdata",
416+
{
417+
"cookies": {},
418+
"data": {"foo": "bar"},
419+
"env": {"SERVER_NAME": "localhost", "SERVER_PORT": "80"},
420+
"headers": {
421+
"Content-Length": "7",
422+
"Content-Type": "application/x-www-form-urlencoded",
423+
"Host": "localhost",
424+
},
425+
"method": "POST",
426+
"query_string": "",
427+
"url": "http://localhost/rest-framework-exc",
428+
},
429+
],
430+
],
431+
)
432+
def test_rest_framework_basic(
433+
sentry_init, client, capture_events, capture_exceptions, type, event_request
434+
):
435+
pytest.importorskip("rest_framework")
436+
sentry_init(integrations=[DjangoIntegration()], send_default_pii=True)
437+
exceptions = capture_exceptions()
438+
events = capture_events()
439+
440+
if type == "json":
441+
client.post(
442+
reverse("rest_framework_exc"),
443+
data=json.dumps({"foo": "bar"}),
444+
content_type="application/json",
445+
)
446+
elif type == "formdata":
447+
client.post(reverse("rest_framework_exc"), data={"foo": "bar"})
448+
else:
449+
assert False
450+
451+
error, = exceptions
452+
assert isinstance(error, ZeroDivisionError)
453+
454+
event, = events
455+
assert event["exception"]["values"][0]["mechanism"]["type"] == "django"
456+
457+
assert event["request"] == event_request

tests/utils/test_general.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
format_and_strip,
1616
strip_string,
1717
filename_for_module,
18+
handle_in_app_impl,
1819
)
1920
from sentry_sdk._compat import text_type
2021

@@ -145,3 +146,24 @@ def test_parse_dsn_paths(given, expected):
145146
def test_parse_invalid_dsn(dsn):
146147
with pytest.raises(BadDsn):
147148
dsn = Dsn(dsn)
149+
150+
151+
@pytest.mark.parametrize("empty", [None, []])
152+
def test_in_app(empty):
153+
assert handle_in_app_impl(
154+
[{"module": "foo"}, {"module": "bar"}],
155+
in_app_include=["foo"],
156+
in_app_exclude=empty,
157+
) == [{"module": "foo", "in_app": True}, {"module": "bar"}]
158+
159+
assert handle_in_app_impl(
160+
[{"module": "foo"}, {"module": "bar"}],
161+
in_app_include=["foo"],
162+
in_app_exclude=["foo"],
163+
) == [{"module": "foo", "in_app": True}, {"module": "bar"}]
164+
165+
assert handle_in_app_impl(
166+
[{"module": "foo"}, {"module": "bar"}],
167+
in_app_include=empty,
168+
in_app_exclude=["foo"],
169+
) == [{"module": "foo", "in_app": False}, {"module": "bar", "in_app": True}]

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ envlist =
4141
deps =
4242
-r test-requirements.txt
4343

44-
py{2.7,3.4,3.5,3.6,3.7}-django: psycopg2>=2.7.5
44+
django-{1.11,2.0,2.1}: djangorestframework>=3.0.0,<4.0.0
4545

4646
django-{1.6,1.7,1.8}: pytest-django<3.0
4747
django-{1.9,1.10,1.11,2.0,2.1,dev}: pytest-django>=3.0

0 commit comments

Comments
 (0)
0