Opened 50 minutes ago
#37500 new Cleanup/optimization
Include the database alias in connection-failure error messages
| Reported by: | Abdul Qoyyuum | Owned by: | |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | 6.1 |
| Severity: | Low | Keywords: | databases, error messages, multiple databases |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
Description
When a project configures more than one entry in DATABASES, a failed
connection raises django.db.utils.OperationalError whose message comes
straight from the database driver and only mentions host and port:
django.db.utils.OperationalError: connection failed: connection to server
at "127.0.0.1", port 5432 failed: Connection refused
Nothing in the exception identifies which database alias failed
(default vs any other entry in DATABASES), nor the configured NAME,
USER, or HOST for that alias. In a multi-database setup the same
host:port may be correct for one alias and wrong for another, so the reader
cannot tell which connection string was actually attempted. This is
especially painful when host/credentials come from environment variables —
a stale or overridden *_HOST can silently point default at localhost
while a second alias points at a remote server, and the error gives no hint
which of the two failed.
Reproduction
DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": "app", "USER": "app", "HOST": "localhost", # nothing listening here "PORT": 5432, }, "ecomdb": { "ENGINE": "django.db.backends.postgresql", "NAME": "etx_ecom", "USER": "report_api", "HOST": "192.168.80.72", "PORT": 5432, }, } User.objects.get_by_natural_key("alice") # uses 'default' EcomRecords.objects.using("ecomdb").exists() # uses 'ecomdb'
Whichever connection fails, the raised message has the same shape; the only
way to know which alias was involved is to read the traceback back to the
call site and cross-check connections[alias].settings_dict manually.
Proposal
Enrich the error as it propagates out of
BaseDatabaseWrapper.ensure_connection() / connect() /
get_new_connection() so the driver error is reported together with the
connection alias and the key settings for that alias, e.g.:
django.db.utils.OperationalError: could not connect to database 'ecomdb'
(postgresql 'etx_ecom' at 192.168.80.72:5432 as 'report_api'):
connection failed: connection to server at "127.0.0.1",
port 5432 failed: Connection refused
An alternative or complementary approach is to attach the context
programmatically instead of only in the message text — e.g.
BaseException.add_note() (Python 3.11+) or an attribute such as
exc.django_connection_alias = self.alias — so that logging,
error-reporting tools, and tests can pick the alias up without parsing the
message.
Notes
django.db.backends.signals.connection_createdexists for successful connections, but there is no equivalent signal or hook on failure. Today the only way to add the alias is to subclass each backend'sDatabaseWrapperunder a customENGINEpath — which also means the workaround must be re-implemented per third-party backend rather than once in Django itself.- Care should be taken not to include the connection
PASSWORDin the enriched message;NAME/USER/HOST/PORTare already visible insettings_dictand match what drivers print today.