Skip to content

Commit cfc9b7f

Browse files
gate database transactions behind a flag
1 parent 6896522 commit cfc9b7f

File tree

2 files changed

+159
-4
lines changed

2 files changed

+159
-4
lines changed

sentry_sdk/integrations/django/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ def __init__(
132132
middleware_spans=True, # type: bool
133133
signals_spans=True, # type: bool
134134
cache_spans=False, # type: bool
135+
database_transaction_spans=False, # type: bool
135136
signals_denylist=None, # type: Optional[list[signals.Signal]]
136137
http_methods_to_capture=DEFAULT_HTTP_METHODS_TO_CAPTURE, # type: tuple[str, ...]
137138
):
@@ -148,6 +149,7 @@ def __init__(
148149
self.signals_denylist = signals_denylist or []
149150

150151
self.cache_spans = cache_spans
152+
self.database_transaction_spans = database_transaction_spans
151153

152154
self.http_methods_to_capture = tuple(map(str.upper, http_methods_to_capture))
153155

@@ -694,6 +696,11 @@ def connect(self):
694696
@ensure_integration_enabled(DjangoIntegration, real_commit)
695697
def _commit(self):
696698
# type: (BaseDatabaseWrapper) -> None
699+
integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
700+
701+
if not integration.database_transaction_spans:
702+
return real_commit(self)
703+
697704
with sentry_sdk.start_span(
698705
op=OP.DB,
699706
name=DBOPERATION.COMMIT,

tests/integrations/django/test_db_transactions.py

Lines changed: 152 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import pytest
3+
import itertools
34
from datetime import datetime
45

56
from django.db import connections
@@ -27,7 +28,9 @@ def client():
2728

2829
@pytest.mark.forked
2930
@pytest_mark_django_db_decorator(transaction=True)
30-
def test_db_no_autocommit_execute(sentry_init, client, capture_events):
31+
def test_db_transaction_spans_disabled_no_autocommit(
32+
sentry_init, client, capture_events
33+
):
3134
sentry_init(
3235
integrations=[DjangoIntegration()],
3336
traces_sample_rate=1.0,
@@ -43,6 +46,151 @@ def test_db_no_autocommit_execute(sentry_init, client, capture_events):
4346

4447
client.get(reverse("postgres_insert_orm_no_autocommit"))
4548

49+
with start_transaction(name="test_transaction"):
50+
from django.db import connection, transaction
51+
52+
cursor = connection.cursor()
53+
54+
query = """INSERT INTO auth_user (
55+
password,
56+
is_superuser,
57+
username,
58+
first_name,
59+
last_name,
60+
email,
61+
is_staff,
62+
is_active,
63+
date_joined
64+
)
65+
VALUES ('password', false, %s, %s, %s, %s, false, true, %s);"""
66+
67+
query_list = (
68+
(
69+
"user1",
70+
"John",
71+
"Doe",
72+
73+
datetime(1970, 1, 1),
74+
),
75+
(
76+
"user2",
77+
"Max",
78+
"Mustermann",
79+
80+
datetime(1970, 1, 1),
81+
),
82+
)
83+
84+
transaction.set_autocommit(False)
85+
cursor.executemany(query, query_list)
86+
transaction.commit()
87+
transaction.set_autocommit(True)
88+
89+
(postgres_spans, sqlite_spans) = events
90+
91+
# Ensure operation is persisted
92+
assert User.objects.using("postgres").exists()
93+
94+
assert postgres_spans["contexts"]["trace"]["origin"] == "auto.http.django"
95+
assert sqlite_spans["contexts"]["trace"]["origin"] == "manual"
96+
97+
commit_spans = [
98+
span
99+
for span in itertools.chain(postgres_spans["spans"], sqlite_spans["spans"])
100+
if span["data"].get(SPANDATA.DB_OPERATION) == DBOPERATION.COMMIT
101+
]
102+
assert len(commit_spans) == 0
103+
104+
105+
@pytest.mark.forked
106+
@pytest_mark_django_db_decorator(transaction=True)
107+
def test_db_transaction_spans_disabled_atomic(sentry_init, client, capture_events):
108+
sentry_init(
109+
integrations=[DjangoIntegration()],
110+
traces_sample_rate=1.0,
111+
)
112+
113+
if "postgres" not in connections:
114+
pytest.skip("postgres tests disabled")
115+
116+
# trigger Django to open a new connection by marking the existing one as None.
117+
connections["postgres"].connection = None
118+
119+
events = capture_events()
120+
121+
client.get(reverse("postgres_insert_orm_atomic"))
122+
123+
with start_transaction(name="test_transaction"):
124+
from django.db import connection, transaction
125+
126+
with transaction.atomic():
127+
cursor = connection.cursor()
128+
129+
query = """INSERT INTO auth_user (
130+
password,
131+
is_superuser,
132+
username,
133+
first_name,
134+
last_name,
135+
email,
136+
is_staff,
137+
is_active,
138+
date_joined
139+
)
140+
VALUES ('password', false, %s, %s, %s, %s, false, true, %s);"""
141+
142+
query_list = (
143+
(
144+
"user1",
145+
"John",
146+
"Doe",
147+
148+
datetime(1970, 1, 1),
149+
),
150+
(
151+
"user2",
152+
"Max",
153+
"Mustermann",
154+
155+
datetime(1970, 1, 1),
156+
),
157+
)
158+
cursor.executemany(query, query_list)
159+
160+
(postgres_spans, sqlite_spans) = events
161+
162+
# Ensure operation is persisted
163+
assert User.objects.using("postgres").exists()
164+
165+
assert postgres_spans["contexts"]["trace"]["origin"] == "auto.http.django"
166+
assert sqlite_spans["contexts"]["trace"]["origin"] == "manual"
167+
168+
commit_spans = [
169+
span
170+
for span in itertools.chain(postgres_spans["spans"], postgres_spans["spans"])
171+
if span["data"].get(SPANDATA.DB_OPERATION) == DBOPERATION.COMMIT
172+
]
173+
assert len(commit_spans) == 0
174+
175+
176+
@pytest.mark.forked
177+
@pytest_mark_django_db_decorator(transaction=True)
178+
def test_db_no_autocommit_execute(sentry_init, client, capture_events):
179+
sentry_init(
180+
integrations=[DjangoIntegration(database_transaction_spans=True)],
181+
traces_sample_rate=1.0,
182+
)
183+
184+
if "postgres" not in connections:
185+
pytest.skip("postgres tests disabled")
186+
187+
# trigger Django to open a new connection by marking the existing one as None.
188+
connections["postgres"].connection = None
189+
190+
events = capture_events()
191+
192+
client.get(reverse("postgres_insert_orm_no_autocommit"))
193+
46194
(event,) = events
47195

48196
# Ensure operation is persisted
@@ -87,7 +235,7 @@ def test_db_no_autocommit_execute(sentry_init, client, capture_events):
87235
@pytest_mark_django_db_decorator(transaction=True)
88236
def test_db_no_autocommit_executemany(sentry_init, client, capture_events):
89237
sentry_init(
90-
integrations=[DjangoIntegration()],
238+
integrations=[DjangoIntegration(database_transaction_spans=True)],
91239
traces_sample_rate=1.0,
92240
)
93241

@@ -171,7 +319,7 @@ def test_db_no_autocommit_executemany(sentry_init, client, capture_events):
171319
@pytest_mark_django_db_decorator(transaction=True)
172320
def test_db_atomic_execute(sentry_init, client, capture_events):
173321
sentry_init(
174-
integrations=[DjangoIntegration()],
322+
integrations=[DjangoIntegration(database_transaction_spans=True)],
175323
traces_sample_rate=1.0,
176324
)
177325

@@ -229,7 +377,7 @@ def test_db_atomic_execute(sentry_init, client, capture_events):
229377
@pytest_mark_django_db_decorator(transaction=True)
230378
def test_db_atomic_executemany(sentry_init, client, capture_events):
231379
sentry_init(
232-
integrations=[DjangoIntegration()],
380+
integrations=[DjangoIntegration(database_transaction_spans=True)],
233381
send_default_pii=True,
234382
traces_sample_rate=1.0,
235383
)

0 commit comments

Comments
 (0)