Skip to content

Commit 5de66e2

Browse files
feat(django): Instrument database rollbacks (#5115)
Add spans for SQL rollbacks issued when Django calls `rollback()` on a PEP-249 database connection. Rollback spans are generated for `transaction.atomic` blocks and for manual `transaction.rollback()` calls when auto-commit is disabled. Tests cover both cases, for SQLite and PostgreSQL, respectively.
1 parent e068db6 commit 5de66e2

File tree

5 files changed

+605
-8
lines changed

5 files changed

+605
-8
lines changed

sentry_sdk/consts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ class INSTRUMENTER:
116116

117117
class SPANNAME:
118118
DB_COMMIT = "COMMIT"
119+
DB_ROLLBACK = "ROLLBACK"
119120

120121

121122
class SPANDATA:

sentry_sdk/integrations/django/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,7 @@ def install_sql_hook():
636636
real_executemany = CursorWrapper.executemany
637637
real_connect = BaseDatabaseWrapper.connect
638638
real_commit = BaseDatabaseWrapper._commit
639+
real_rollback = BaseDatabaseWrapper._rollback
639640
except AttributeError:
640641
# This won't work on Django versions < 1.6
641642
return
@@ -708,10 +709,26 @@ def _commit(self):
708709
_set_db_data(span, self, SPANNAME.DB_COMMIT)
709710
return real_commit(self)
710711

712+
def _rollback(self):
713+
# type: (BaseDatabaseWrapper) -> None
714+
integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
715+
716+
if integration is None or not integration.db_transaction_spans:
717+
return real_rollback(self)
718+
719+
with sentry_sdk.start_span(
720+
op=OP.DB,
721+
name=SPANNAME.DB_ROLLBACK,
722+
origin=DjangoIntegration.origin_db,
723+
) as span:
724+
_set_db_data(span, self, SPANNAME.DB_ROLLBACK)
725+
return real_rollback(self)
726+
711727
CursorWrapper.execute = execute
712728
CursorWrapper.executemany = executemany
713729
BaseDatabaseWrapper.connect = connect
714730
BaseDatabaseWrapper._commit = _commit
731+
BaseDatabaseWrapper._rollback = _rollback
715732
ignore_logger("django.db.backends")
716733

717734

tests/integrations/django/myapp/urls.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,26 @@ def path(path, *args, **kwargs):
6666
views.postgres_insert_orm_no_autocommit,
6767
name="postgres_insert_orm_no_autocommit",
6868
),
69+
path(
70+
"postgres-insert-no-autocommit-rollback",
71+
views.postgres_insert_orm_no_autocommit_rollback,
72+
name="postgres_insert_orm_no_autocommit_rollback",
73+
),
6974
path(
7075
"postgres-insert-atomic",
7176
views.postgres_insert_orm_atomic,
7277
name="postgres_insert_orm_atomic",
7378
),
79+
path(
80+
"postgres-insert-atomic-rollback",
81+
views.postgres_insert_orm_atomic_rollback,
82+
name="postgres_insert_orm_atomic_rollback",
83+
),
84+
path(
85+
"postgres-insert-atomic-exception",
86+
views.postgres_insert_orm_atomic_exception,
87+
name="postgres_insert_orm_atomic_exception",
88+
),
7489
path(
7590
"postgres-select-slow-from-supplement",
7691
helper_views.postgres_select_orm,

tests/integrations/django/myapp/views.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,23 @@ def postgres_insert_orm_no_autocommit(request, *args, **kwargs):
264264
return HttpResponse("ok {}".format(user))
265265

266266

267+
@csrf_exempt
268+
def postgres_insert_orm_no_autocommit_rollback(request, *args, **kwargs):
269+
transaction.set_autocommit(False, using="postgres")
270+
try:
271+
user = User.objects.db_manager("postgres").create_user(
272+
username="user1",
273+
)
274+
transaction.rollback(using="postgres")
275+
except Exception:
276+
transaction.rollback(using="postgres")
277+
transaction.set_autocommit(True, using="postgres")
278+
raise
279+
280+
transaction.set_autocommit(True, using="postgres")
281+
return HttpResponse("ok {}".format(user))
282+
283+
267284
@csrf_exempt
268285
def postgres_insert_orm_atomic(request, *args, **kwargs):
269286
with transaction.atomic(using="postgres"):
@@ -273,6 +290,30 @@ def postgres_insert_orm_atomic(request, *args, **kwargs):
273290
return HttpResponse("ok {}".format(user))
274291

275292

293+
@csrf_exempt
294+
def postgres_insert_orm_atomic_rollback(request, *args, **kwargs):
295+
with transaction.atomic(using="postgres"):
296+
user = User.objects.db_manager("postgres").create_user(
297+
username="user1",
298+
)
299+
transaction.set_rollback(True, using="postgres")
300+
return HttpResponse("ok {}".format(user))
301+
302+
303+
@csrf_exempt
304+
def postgres_insert_orm_atomic_exception(request, *args, **kwargs):
305+
try:
306+
with transaction.atomic(using="postgres"):
307+
user = User.objects.db_manager("postgres").create_user(
308+
username="user1",
309+
)
310+
transaction.set_rollback(True, using="postgres")
311+
1 / 0
312+
except ZeroDivisionError:
313+
pass
314+
return HttpResponse("ok {}".format(user))
315+
316+
276317
@csrf_exempt
277318
def permission_denied_exc(*args, **kwargs):
278319
raise PermissionDenied("bye")

0 commit comments

Comments
 (0)