Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d2552ec
feat(django): Instrument database commits
alexander-alderman-webb Nov 13, 2025
58ecb5b
.
alexander-alderman-webb Nov 13, 2025
ad5ef1d
tests
alexander-alderman-webb Nov 18, 2025
7a82862
.
alexander-alderman-webb Nov 18, 2025
e8f664d
.
alexander-alderman-webb Nov 18, 2025
943e3bb
.
alexander-alderman-webb Nov 18, 2025
e397f65
.
alexander-alderman-webb Nov 18, 2025
3c4127c
.
alexander-alderman-webb Nov 19, 2025
7972103
.
alexander-alderman-webb Nov 19, 2025
639182d
wrap _commit instead of commit
alexander-alderman-webb Nov 20, 2025
2c90889
verify query and commit are siblings
alexander-alderman-webb Nov 21, 2025
f9005dd
fix indentation
alexander-alderman-webb Nov 21, 2025
52fe140
try-finally for resetting autocommit
alexander-alderman-webb Nov 21, 2025
6896522
.
alexander-alderman-webb Nov 21, 2025
cfc9b7f
gate database transactions behind a flag
alexander-alderman-webb Nov 24, 2025
9b503a0
:
alexander-alderman-webb Nov 24, 2025
f586a56
.
alexander-alderman-webb Nov 24, 2025
f0f7b40
rollback on exception in sample app
alexander-alderman-webb Nov 24, 2025
b0b9bd7
use postgres in rollback on exception
alexander-alderman-webb Nov 24, 2025
3ccd0c3
remove redundant annotation
alexander-alderman-webb Nov 24, 2025
89c9594
rename database_transaction_spans to db_transaction_spans
alexander-alderman-webb Nov 24, 2025
2668f4e
rename DBOPERATION to SPANNAME
alexander-alderman-webb Nov 24, 2025
28d7050
update import in tests
alexander-alderman-webb Nov 24, 2025
485dc4a
rename COMMIT to DB_COMMIT
alexander-alderman-webb Nov 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions sentry_sdk/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ class INSTRUMENTER:
OTEL = "otel"


class DBOPERATION:
COMMIT = "COMMIT"


class SPANDATA:
"""
Additional information describing the type of the span.
Expand Down
22 changes: 19 additions & 3 deletions sentry_sdk/integrations/django/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from importlib import import_module

import sentry_sdk
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.consts import OP, SPANDATA, DBOPERATION
from sentry_sdk.scope import add_global_event_processor, should_send_default_pii
from sentry_sdk.serializer import add_global_repr_processor, add_repr_sequence_type
from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource
Expand Down Expand Up @@ -633,6 +633,7 @@ def install_sql_hook():
real_execute = CursorWrapper.execute
real_executemany = CursorWrapper.executemany
real_connect = BaseDatabaseWrapper.connect
real_commit = BaseDatabaseWrapper._commit
except AttributeError:
# This won't work on Django versions < 1.6
return
Expand Down Expand Up @@ -690,18 +691,33 @@ def connect(self):
_set_db_data(span, self)
return real_connect(self)

@ensure_integration_enabled(DjangoIntegration, real_commit)
def _commit(self):
# type: (BaseDatabaseWrapper) -> None
with sentry_sdk.start_span(
op=OP.DB,
name=DBOPERATION.COMMIT,
origin=DjangoIntegration.origin_db,
) as span:
_set_db_data(span, self, DBOPERATION.COMMIT)
return real_commit(self)

CursorWrapper.execute = execute
CursorWrapper.executemany = executemany
BaseDatabaseWrapper.connect = connect
BaseDatabaseWrapper._commit = _commit
ignore_logger("django.db.backends")


def _set_db_data(span, cursor_or_db):
# type: (Span, Any) -> None
def _set_db_data(span, cursor_or_db, db_operation=None):
# type: (Span, Any, Optional[str]) -> None
db = cursor_or_db.db if hasattr(cursor_or_db, "db") else cursor_or_db
vendor = db.vendor
span.set_data(SPANDATA.DB_SYSTEM, vendor)

if db_operation is not None:
span.set_data(SPANDATA.DB_OPERATION, db_operation)

# Some custom backends override `__getattr__`, making it look like `cursor_or_db`
# actually has a `connection` and the `connection` has a `get_dsn_parameters`
# attribute, only to throw an error once you actually want to call it.
Expand Down
10 changes: 10 additions & 0 deletions tests/integrations/django/myapp/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ def path(path, *args, **kwargs):
path("template-test4", views.template_test4, name="template_test4"),
path("postgres-select", views.postgres_select, name="postgres_select"),
path("postgres-select-slow", views.postgres_select_orm, name="postgres_select_orm"),
path(
"postgres-insert-no-autocommit",
views.postgres_insert_orm_no_autocommit,
name="postgres_insert_orm_no_autocommit",
),
path(
"postgres-insert-atomic",
views.postgres_insert_orm_atomic,
name="postgres_insert_orm_atomic",
),
path(
"postgres-select-slow-from-supplement",
helper_views.postgres_select_orm,
Expand Down
22 changes: 22 additions & 0 deletions tests/integrations/django/myapp/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import json
import threading

from django.db import transaction
from django.contrib.auth import login
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
Expand Down Expand Up @@ -246,6 +247,27 @@ def postgres_select_orm(request, *args, **kwargs):
return HttpResponse("ok {}".format(user))


@csrf_exempt
def postgres_insert_orm_no_autocommit(request, *args, **kwargs):
transaction.set_autocommit(False, using="postgres")
user = User.objects.db_manager("postgres").create_user(
username="user1",
)
transaction.commit(using="postgres")
transaction.set_autocommit(True, using="postgres")

return HttpResponse("ok {}".format(user))


@csrf_exempt
def postgres_insert_orm_atomic(request, *args, **kwargs):
with transaction.atomic(using="postgres"):
user = User.objects.db_manager("postgres").create_user(
username="user1",
)
return HttpResponse("ok {}".format(user))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Duplicate username causes IntegrityError on repeated calls

Both postgres_insert_orm_no_autocommit and postgres_insert_orm_atomic views create users with the hardcoded username "user1". When these endpoints are called more than once (either the same endpoint twice or both endpoints once), Django raises an IntegrityError because usernames must be unique. This prevents manual testing as described in the PR description and makes the endpoints non-idempotent. The views need to either generate unique usernames, delete existing users first, or handle the integrity constraint violation.

Fix in Cursor Fix in Web

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests should be as deterministic as possible. The sample is intended to be very minimal.



@csrf_exempt
def permission_denied_exc(*args, **kwargs):
raise PermissionDenied("bye")
Expand Down
Loading
Loading