diff --git a/sentry_sdk/integrations/django/__init__.py b/sentry_sdk/integrations/django/__init__.py index 03d0545b1d..c82ef4f148 100644 --- a/sentry_sdk/integrations/django/__init__.py +++ b/sentry_sdk/integrations/django/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import +import inspect import sys import threading import weakref @@ -665,12 +666,21 @@ def _set_db_data(span, cursor_or_db): vendor = db.vendor span.set_data(SPANDATA.DB_SYSTEM, vendor) - connection_params = ( - cursor_or_db.connection.get_dsn_parameters() - if hasattr(cursor_or_db, "connection") + if ( + hasattr(cursor_or_db, "connection") and hasattr(cursor_or_db.connection, "get_dsn_parameters") - else db.get_connection_params() - ) + and inspect.isfunction(cursor_or_db.connection.get_dsn_parameters) + ): + # 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. + # Hence the `inspect` check whether `get_dsn_parameters` is an actual callable + # function. + connection_params = cursor_or_db.connection.get_dsn_parameters() + + else: + connection_params = db.get_connection_params() + db_name = connection_params.get("dbname") or connection_params.get("database") if db_name is not None: span.set_data(SPANDATA.DB_NAME, db_name) diff --git a/tests/integrations/django/test_basic.py b/tests/integrations/django/test_basic.py index 379c4d9614..e599c78843 100644 --- a/tests/integrations/django/test_basic.py +++ b/tests/integrations/django/test_basic.py @@ -22,10 +22,11 @@ from sentry_sdk._compat import PY2, PY310 from sentry_sdk import capture_message, capture_exception, configure_scope from sentry_sdk.consts import SPANDATA -from sentry_sdk.integrations.django import DjangoIntegration +from sentry_sdk.integrations.django import DjangoIntegration, _set_db_data from sentry_sdk.integrations.django.signals_handlers import _get_receiver_name from sentry_sdk.integrations.django.caching import _get_span_description from sentry_sdk.integrations.executing import ExecutingIntegration +from sentry_sdk.tracing import Span from tests.integrations.django.myapp.wsgi import application from tests.integrations.django.utils import pytest_mark_django_db_decorator @@ -656,6 +657,24 @@ def test_db_connection_span_data(sentry_init, client, capture_events): assert data.get(SPANDATA.SERVER_PORT) == "5432" +def test_set_db_data_custom_backend(): + class DummyBackend(object): + # https://github.com/mongodb/mongo-python-driver/blob/6ffae5522c960252b8c9adfe2a19b29ff28187cb/pymongo/collection.py#L126 + def __getattr__(self, attr): + return self + + def __call__(self): + raise TypeError + + def get_connection_params(self): + return {} + + try: + _set_db_data(Span(), DummyBackend()) + except TypeError: + pytest.fail("A TypeError was raised") + + @pytest.mark.parametrize( "transaction_style,client_url,expected_transaction,expected_source,expected_response", [