Skip to content

Commit 4ffebb0

Browse files
authored
PYTHON-5168 Use logging for client background task errors (#2166)
1 parent 4ed621b commit 4ffebb0

File tree

5 files changed

+62
-8
lines changed

5 files changed

+62
-8
lines changed

pymongo/asynchronous/mongo_client.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
_async_create_lock,
8989
_release_locks,
9090
)
91-
from pymongo.logger import _CLIENT_LOGGER, _log_or_warn
91+
from pymongo.logger import _CLIENT_LOGGER, _log_client_error, _log_or_warn
9292
from pymongo.message import _CursorAddress, _GetMore, _Query
9393
from pymongo.monitoring import ConnectionClosedReason
9494
from pymongo.operations import (
@@ -2049,7 +2049,7 @@ async def _process_kill_cursors(self) -> None:
20492049
# can be caught in _process_periodic_tasks
20502050
raise
20512051
else:
2052-
helpers_shared._handle_exception()
2052+
_log_client_error()
20532053

20542054
# Don't re-open topology if it's closed and there's no pending cursors.
20552055
if address_to_cursor_ids:
@@ -2061,7 +2061,7 @@ async def _process_kill_cursors(self) -> None:
20612061
if isinstance(exc, InvalidOperation) and self._topology._closed:
20622062
raise
20632063
else:
2064-
helpers_shared._handle_exception()
2064+
_log_client_error()
20652065

20662066
# This method is run periodically by a background thread.
20672067
async def _process_periodic_tasks(self) -> None:
@@ -2075,7 +2075,7 @@ async def _process_periodic_tasks(self) -> None:
20752075
if isinstance(exc, InvalidOperation) and self._topology._closed:
20762076
return
20772077
else:
2078-
helpers_shared._handle_exception()
2078+
_log_client_error()
20792079

20802080
def _return_server_session(
20812081
self, server_session: Union[_ServerSession, _EmptyServerSession]

pymongo/logger.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@ class _SDAMStatusMessage(str, enum.Enum):
9696
}
9797

9898

99+
def _log_client_error() -> None:
100+
# This is called from a daemon thread so check for None to account for interpreter shutdown.
101+
logger = _CLIENT_LOGGER
102+
if logger:
103+
# logger.exception includes the full traceback.
104+
logger.exception("MongoClient background task encountered an error:")
105+
106+
99107
def _debug_log(logger: logging.Logger, **fields: Any) -> None:
100108
logger.debug(LogMessage(**fields))
101109

pymongo/synchronous/mongo_client.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
_create_lock,
8181
_release_locks,
8282
)
83-
from pymongo.logger import _CLIENT_LOGGER, _log_or_warn
83+
from pymongo.logger import _CLIENT_LOGGER, _log_client_error, _log_or_warn
8484
from pymongo.message import _CursorAddress, _GetMore, _Query
8585
from pymongo.monitoring import ConnectionClosedReason
8686
from pymongo.operations import (
@@ -2043,7 +2043,7 @@ def _process_kill_cursors(self) -> None:
20432043
# can be caught in _process_periodic_tasks
20442044
raise
20452045
else:
2046-
helpers_shared._handle_exception()
2046+
_log_client_error()
20472047

20482048
# Don't re-open topology if it's closed and there's no pending cursors.
20492049
if address_to_cursor_ids:
@@ -2055,7 +2055,7 @@ def _process_kill_cursors(self) -> None:
20552055
if isinstance(exc, InvalidOperation) and self._topology._closed:
20562056
raise
20572057
else:
2058-
helpers_shared._handle_exception()
2058+
_log_client_error()
20592059

20602060
# This method is run periodically by a background thread.
20612061
def _process_periodic_tasks(self) -> None:
@@ -2069,7 +2069,7 @@ def _process_periodic_tasks(self) -> None:
20692069
if isinstance(exc, InvalidOperation) and self._topology._closed:
20702070
return
20712071
else:
2072-
helpers_shared._handle_exception()
2072+
_log_client_error()
20732073

20742074
def _return_server_session(
20752075
self, server_session: Union[_ServerSession, _EmptyServerSession]

test/asynchronous/test_client.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1790,6 +1790,29 @@ async def stall_connect(*args, **kwargs):
17901790
# Each ping command should not take more than 2 seconds
17911791
self.assertLess(total, 2)
17921792

1793+
async def test_background_connections_log_on_error(self):
1794+
with self.assertLogs("pymongo.client", level="ERROR") as cm:
1795+
client = await self.async_rs_or_single_client(minPoolSize=1)
1796+
# Create a single connection in the pool.
1797+
await client.admin.command("ping")
1798+
1799+
# Cause new connections to fail.
1800+
pool = await async_get_pool(client)
1801+
1802+
async def fail_connect(*args, **kwargs):
1803+
raise Exception("failed to connect")
1804+
1805+
pool.connect = fail_connect
1806+
# Un-patch Pool.connect to break the cyclic reference.
1807+
self.addCleanup(delattr, pool, "connect")
1808+
1809+
await pool.reset_without_pause()
1810+
1811+
await async_wait_until(
1812+
lambda: "failed to connect" in "".join(cm.output), "start creating connections"
1813+
)
1814+
self.assertIn("MongoClient background task encountered an error", "".join(cm.output))
1815+
17931816
@async_client_context.require_replica_set
17941817
async def test_direct_connection(self):
17951818
# direct_connection=True should result in Single topology.

test/test_client.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1747,6 +1747,29 @@ def stall_connect(*args, **kwargs):
17471747
# Each ping command should not take more than 2 seconds
17481748
self.assertLess(total, 2)
17491749

1750+
def test_background_connections_log_on_error(self):
1751+
with self.assertLogs("pymongo.client", level="ERROR") as cm:
1752+
client = self.rs_or_single_client(minPoolSize=1)
1753+
# Create a single connection in the pool.
1754+
client.admin.command("ping")
1755+
1756+
# Cause new connections to fail.
1757+
pool = get_pool(client)
1758+
1759+
def fail_connect(*args, **kwargs):
1760+
raise Exception("failed to connect")
1761+
1762+
pool.connect = fail_connect
1763+
# Un-patch Pool.connect to break the cyclic reference.
1764+
self.addCleanup(delattr, pool, "connect")
1765+
1766+
pool.reset_without_pause()
1767+
1768+
wait_until(
1769+
lambda: "failed to connect" in "".join(cm.output), "start creating connections"
1770+
)
1771+
self.assertIn("MongoClient background task encountered an error", "".join(cm.output))
1772+
17501773
@client_context.require_replica_set
17511774
def test_direct_connection(self):
17521775
# direct_connection=True should result in Single topology.

0 commit comments

Comments
 (0)