8000 record span and breadcrumb when Django opens db connection (#1250) · anilktechie/sentry-python@df542a2 · GitHub
[go: up one dir, main page]

Skip to content

Commit df542a2

Browse files
authored
record span and breadcrumb when Django opens db connection (getsentry#1250)
1 parent 5ca8466 commit df542a2

File tree

4 files changed

+108
-6
lines changed

4 files changed

+108
-6
lines changed

sentry_sdk/integrations/django/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,9 +481,17 @@ def install_sql_hook():
481481
except ImportError:
482482
from django.db.backends.util import CursorWrapper
483483

484+
try:
485+
# django 1.6 and 1.7 compatability
486+
from django.db.backends import BaseDatabaseWrapper
487+
except ImportError:
488+
# django 1.8 or later
489+
from django.db.backends.base.base import BaseDatabaseWrapper
490+
484491
try:
485492
real_execute = CursorWrapper.execute
486493
real_executemany = CursorWrapper.executemany
494+
real_connect = BaseDatabaseWrapper.connect
487495
except AttributeError:
488496
# This won't work on Django versions < 1.6
489497
return
@@ -510,6 +518,19 @@ def executemany(self, sql, param_list):
510518
):
511519
return real_executemany(self, sql, param_list)
512520

521+
def connect(self):
522+
# type: (BaseDatabaseWrapper) -> None
523+
hub = Hub.current
524+
if hub.get_integration(DjangoIntegration) is None:
525+
return real_connect(self)
526+
527+
with capture_internal_exceptions():
528+
hub.add_breadcrumb(message="connect", category="query")
529+
530+
with hub.start_span(op="db", description="connect"):
531+
return real_connect(self)
532+
513533
CursorWrapper.execute = execute
514534
CursorWrapper.executemany = executemany
535+
BaseDatabaseWrapper.connect = connect
515536
ignore_logger("django.db.backends")

tests/integrations/django/myapp/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def path(path, *args, **kwargs):
4747
path("template-exc", views.template_exc, name="template_exc"),
4848
path("template-test", views.template_test, name="template_test"),
4949
path("template-test2", views.template_test2, name="template_test2"),
50+
path("postgres-select", views.postgres_select, name="postgres_select"),
5051
path(
5152
"permission-denied-exc",
5253
views.permission_denied_exc,

tests/integrations/django/myapp/views.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,15 @@ def template_test2(request, *args, **kwargs):
127127
)
128128

129129

130+
@csrf_exempt
131+
def postgres_select(request, *args, **kwargs):
132+
from django.db import connections
133+
134+
cursor = connections["postgres"].cursor()
135+
cursor.execute("SELECT 1;")
136+
return HttpResponse("ok")
137+
138+
130139
@csrf_exempt
131140
def permission_denied_exc(*args, **kwargs):
132141
raise PermissionDenied("bye")

tests/integrations/django/test_basic.py

Lines changed: 77 additions & 6 deletions
< 8000 /tr>
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,24 @@
1919

2020
from sentry_sdk import capture_message, capture_exception, configure_scope
2121
from sentry_sdk.integrations.django import DjangoIntegration
22+
from functools import partial
2223

2324
from tests.integrations.django.myapp.wsgi import application
2425

2526
# Hack to prevent from experimental feature introduced in version `4.3.0` in `pytest-django` that
2627
# requires explicit database allow from failing the test
27-
pytest_mark_django_db_decorator = pytest.mark.django_db
28+
pytest_mark_django_db_decorator = partial(pytest.mark.django_db)
2829
try:
2930
pytest_version = tuple(map(int, pytest_django.__version__.split(".")))
3031
if pytest_version > (4, 2, 0):
31-
pytest_mark_django_db_decorator = pytest.mark.django_db(databases="__all__")
32+
pytest_mark_django_db_decorator = partial(
33+
pytest.mark.django_db, databases="__all__"
34+
)
3235
except ValueError:
3336
if "dev" in pytest_django.__version__:
34-
pytest_mark_django_db_decorator = pytest.mark.django_db(databases="__all__")
37+
pytest_mark_django_db_decorator = partial(
38+
pytest.mark.django_db, databases="__all__"
39+
)
3540
except AttributeError:
3641
pass
3742

@@ -259,7 +264,7 @@ def test_sql_queries(sentry_init, capture_events, with_integration):
259264

260265

261266
@pytest.mark.forked
262-
@pytest_mark_django_db_decorator
267+
@pytest_mark_django_db_decorator()
263268
def test_sql_dict_query_params(sentry_init, capture_events):
264269
sentry_init(
265270
integrations=[DjangoIntegration()],
@@ -304,7 +309,7 @@ def test_sql_dict_query_params(sentry_init, capture_events):
304309
],
305310
)
306311
@pytest.mark.forked
307-
@pytest_mark_django_db_decorator
312+
@pytest_mark_django_db_decorator()
308313
def test_sql_psycopg2_string_composition(sentry_init, capture_events, query):
309314
sentry_init(
310315
integrations=[DjangoIntegration()],
@@ -337,7 +342,7 @@ def test_sql_psycopg2_string_composition(sentry_init, capture_events, query):
337342

338343

339344
@pytest.mark.forked
340-
@pytest_mark_django_db_decorator
345+
@pytest_mark_django_db_decorator()
341346
def test_sql_psycopg2_placeholders(sentry_init, capture_events):
342347
sentry_init(
343348
integrations=[DjangoIntegration()],
@@ -397,6 +402,72 @@ def test_sql_psycopg2_placeholders(sentry_init, capture_events):
397402
]
398403

399404

405+
@pytest.mark.forked
406+
@pytest_mark_django_db_decorator(transaction=True)
407+
def test_django_connect_trace(sentry_init, client, capture_events, render_span_tree):
408+
"""
409+
Verify we record a span when opening a new database.
410+
"""
411+
sentry_init(
412+
integrations=[DjangoIntegration()],
413+
send_default_pii=True,
414+
traces_sample_rate=1.0,
415+
)
416+
417+
from django.db import connections
418+
419+
if "postgres" not in connections:
420+
pytest.skip("postgres tests disabled")
421+
422+
# trigger Django to open a new connection by marking the existing one as None.
423+
connections["postgres"].connection = None
424+
425+
events = capture_events()
426+
427+
content, status, headers = client.get(reverse("postgres_select"))
428+
assert status == "200 OK"
429+
430+
assert '- op="db": description="connect"' in render_span_tree(events[0])
431+
432+
433+
@pytest.mark.forked
434+
@pytest_mark_django_db_decorator(transaction=True)
435+
def test_django_connect_breadcrumbs(
436+
sentry_init, client, capture_events, render_span_tree
437+
):
438+
"""
439+
Verify we record a breadcrumb when opening a new database.
440+
"""
441+
sentry_init(
442+
integrations=[DjangoIntegration()],
443+
send_default_pii=True,
444+
)
445+
446+
from django.db import connections
447+
448+
if "postgres" not in connections:
449+
pytest.skip("postgres tests disabled")
450+
451+
# trigger Django to open a new connection by marking the existing one as None.
452+
connections["postgres"].connection = None
453+
454+
events = capture_events()
455+
456+
cursor = connections["postgres"].cursor()
457+
cursor.execute("select 1")
458+
459+
# trigger recording of event.
460+
capture_message("HI")
461+
(event,) = events
462+
for crumb in event["breadcrumbs"]["values"]:
463+
del crumb["timestamp"]
464+
465+
assert event["breadcrumbs"]["values"][-2:] == [
466+
{"message": "connect", "category": "query", "type": "default"},
467+
{"message": "select 1", "category": "query", "data": {}, "type": "default"},
468+
]
469+
470+
400471
@pytest.mark.parametrize(
401472
"transaction_style,expected_transaction",
402473
[

0 commit comments

Comments
 (0)
0