Skip to content

Commit 8182704

Browse files
committed
trying to fetch connection parameters on startup
1 parent c51ee08 commit 8182704

File tree

1 file changed

+110
-46
lines changed

1 file changed

+110
-46
lines changed

sentry_sdk/integrations/django/__init__.py

Lines changed: 110 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import inspect
21
import sys
32
import threading
43
import weakref
@@ -30,6 +29,9 @@
3029
RequestExtractor,
3130
)
3231

32+
# Global cache for database configurations to avoid connection pool interference
33+
_cached_db_configs = {} # type: Dict[str, Dict[str, Any]]
34+
3335
try:
3436
from django import VERSION as DJANGO_VERSION
3537
from django.conf import settings as django_settings
@@ -156,6 +158,9 @@ def setup_once():
156158
# type: () -> None
157159
_check_minimum_version(DjangoIntegration, DJANGO_VERSION)
158160

161+
# Cache database configurations to avoid connection pool interference
162+
_cache_database_configurations()
163+
159164
install_sql_hook()
160165
# Patch in our custom middleware.
161166

@@ -614,6 +619,53 @@ def _set_user_info(request, event):
614619
pass
615620

616621

622+
def _cache_database_configurations():
623+
# type: () -> None
624+
"""Cache database configurations from Django settings to avoid connection pool interference."""
625+
global _cached_db_configs
626+
627+
try:
628+
from django.conf import settings
629+
from django.db import connections
630+
631+
for alias, db_config in settings.DATABASES.items():
632+
if not db_config: # Skip empty default configs
633+
continue
634+
635+
try:
636+
# Get database wrapper to access vendor info
637+
db_wrapper = connections[alias]
638+
639+
_cached_db_configs[alias] = {
640+
"db_name": db_config.get("NAME"),
641+
"host": db_config.get("HOST", "localhost"),
642+
"port": db_config.get("PORT"),
643+
"vendor": db_wrapper.vendor,
644+
"unix_socket": db_config.get("OPTIONS", {}).get("unix_socket"),
645+
"engine": db_config.get("ENGINE"),
646+
}
647+
648+
logger.debug(
649+
"Cached database configuration for %s: %s",
650+
alias,
651+
{
652+
k: v
653+
for k, v in _cached_db_configs[alias].items()
654+
if k != "vendor"
655+
},
656+
)
657+
658+
except Exception as e:
659+
logger.debug(
660+
"Failed to cache database configuration for %s: %s", alias, e
661+
)
662+
663+
except Exception as e:
664+
logger.debug("Failed to cache database configurations: %s", e)
665+
# Graceful fallback - cache remains empty
666+
_cached_db_configs = {}
667+
668+
617669
def install_sql_hook():
618670
# type: () -> None
619671
"""If installed this causes Django's queries to be captured."""
@@ -698,54 +750,66 @@ def connect(self):
698750

699751
def _set_db_data(span, cursor_or_db):
700752
# type: (Span, Any) -> None
701-
db = cursor_or_db.db if hasattr(cursor_or_db, "db") else cursor_or_db
702-
vendor = db.vendor
703-
span.set_data(SPANDATA.DB_SYSTEM, vendor)
704-
705-
# Some custom backends override `__getattr__`, making it look like `cursor_or_db`
706-
# actually has a `connection` and the `connection` has a `get_dsn_parameters`
707-
# attribute, only to throw an error once you actually want to call it.
708-
# Hence the `inspect` check whether `get_dsn_parameters` is an actual callable
709-
# function.
710-
is_psycopg2 = (
711-
hasattr(cursor_or_db, "connection")
712-
and hasattr(cursor_or_db.connection, "get_dsn_parameters")
713-
and inspect.isroutine(cursor_or_db.connection.get_dsn_parameters)
714-
)
715-
if is_psycopg2:
716-
connection_params = cursor_or_db.connection.get_dsn_parameters()
717-
else:
718-
try:
719-
# psycopg3, only extract needed params as get_parameters
720-
# can be slow because of the additional logic to filter out default
721-
# values
722-
connection_params = {
723-
"dbname": cursor_or_db.connection.info.dbname,
724-
"port": cursor_or_db.connection.info.port,
725-
}
726-
# PGhost returns host or base dir of UNIX socket as an absolute path
727-
# starting with /, use it only when it contains host
728-
pg_host = cursor_or_db.connection.info.host
729-
if pg_host and not pg_host.startswith("/"):
730-
connection_params["host"] = pg_host
731-
except Exception:
732-
connection_params = db.get_connection_params()
733-
734-
db_name = connection_params.get("dbname") or connection_params.get("database")
735-
if db_name is not None:
736-
span.set_data(SPANDATA.DB_NAME, db_name)
753+
"""
754+
Improved version that avoids connection pool interference by using cached
755+
database configurations and specific exception handling.
756+
"""
757+
from django.core.exceptions import ImproperlyConfigured
737758

738-
server_address = connection_params.get("host")
739-
if server_address is not None:
740-
span.set_data(SPANDATA.SERVER_ADDRESS, server_address)
759+
# Get database wrapper
760+
db = cursor_or_db.db if hasattr(cursor_or_db, "db") else cursor_or_db
741761

742-
server_port = connection_params.get("port")
743-
if server_port is not None:
744-
span.set_data(SPANDATA.SERVER_PORT, str(server_port))
762+
# Set vendor (always safe, no connection access)
763+
span.set_data(SPANDATA.DB_SYSTEM, db.vendor)
764+
765+
# Get the database alias (supports .using() queries)
766+
db_alias = db.alias
767+
768+
# Method 1: Use pre-cached configuration (FASTEST, NO CONNECTION ACCESS)
769+
cached_config = _cached_db_configs.get(db_alias)
770+
if cached_config:
771+
if cached_config["db_name"]:
772+
span.set_data(SPANDATA.DB_NAME, cached_config["db_name"])
773+
if cached_config["host"]:
774+
span.set_data(SPANDATA.SERVER_ADDRESS, cached_config["host"])
775+
if cached_config["port"]:
776+
span.set_data(SPANDATA.SERVER_PORT, str(cached_config["port"]))
777+
if cached_config["unix_socket"]:
778+
span.set_data(SPANDATA.SERVER_SOCKET_ADDRESS, cached_config["unix_socket"])
779+
return
745780

746-
server_socket_address = connection_params.get("unix_socket")
747-
if server_socket_address is not None:
748-
span.set_data(SPANDATA.SERVER_SOCKET_ADDRESS, server_socket_address)
781+
# Method 2: Dynamic fallback using get_connection_params() (NO CONNECTION ACCESS)
782+
try:
783+
connection_params = db.get_connection_params()
784+
785+
# Extract database name
786+
db_name = connection_params.get("dbname") or connection_params.get("database")
787+
if db_name:
788+
span.set_data(SPANDATA.DB_NAME, db_name)
789+
790+
# Extract host
791+
host = connection_params.get("host")
792+
if host:
793+
span.set_data(SPANDATA.SERVER_ADDRESS, host)
794+
795+
# Extract port
796+
port = connection_params.get("port")
797+
if port:
798+
span.set_data(SPANDATA.SERVER_PORT, str(port))
799+
800+
# Extract unix socket
801+
unix_socket = connection_params.get("unix_socket")
802+
if unix_socket:
803+
span.set_data(SPANDATA.SERVER_SOCKET_ADDRESS, unix_socket)
804+
805+
except (KeyError, ImproperlyConfigured, AttributeError) as e:
806+
# Specific exceptions that get_connection_params() can raise:
807+
# - KeyError: Missing required database settings (most common)
808+
# - ImproperlyConfigured: Django configuration problems
809+
# - AttributeError: Missing methods on database wrapper
810+
logger.debug("Failed to get database connection params for %s: %s", db_alias, e)
811+
# Skip database metadata rather than risk connection issues
812+
pass
749813

750814

751815
def add_template_context_repr_sequence():

0 commit comments

Comments
 (0)