gh-132106: Allow logging.handlers.QueueListener to be used as a context manager (#132107)

This commit is contained in:
Charles Machalow 2025-04-12 05:00:04 -07:00 committed by GitHub
parent ad3bbe8fbc
commit 517e96b9ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 54 additions and 6 deletions

View file

@ -626,6 +626,19 @@ which, when run, will produce:
of each message with the handler's level, and only passes a message to a of each message with the handler's level, and only passes a message to a
handler if it's appropriate to do so. handler if it's appropriate to do so.
.. versionchanged:: next
The :class:`QueueListener` can be started (and stopped) via the
:keyword:`with` statement. For example:
.. code-block:: python
with QueueListener(que, handler) as listener:
# The queue listener automatically starts
# when the 'with' block is entered.
pass
# The queue listener automatically stops once
# the 'with' block is exited.
.. _network-logging: .. _network-logging:
Sending and receiving logging events across a network Sending and receiving logging events across a network

View file

@ -1148,6 +1148,13 @@ possible, while any potentially slow operations (such as sending an email via
.. versionchanged:: 3.5 .. versionchanged:: 3.5
The ``respect_handler_level`` argument was added. The ``respect_handler_level`` argument was added.
.. versionchanged:: next
:class:`QueueListener` can now be used as a context manager via
:keyword:`with`. When entering the context, the listener is started. When
exiting the context, the listener is stopped.
:meth:`~contextmanager.__enter__` returns the
:class:`QueueListener` object.
.. method:: dequeue(block) .. method:: dequeue(block)
Dequeues a record and return it, optionally blocking. Dequeues a record and return it, optionally blocking.

View file

@ -812,6 +812,14 @@ linecache
(Contributed by Tian Gao in :gh:`131638`.) (Contributed by Tian Gao in :gh:`131638`.)
logging.handlers
----------------
* :class:`logging.handlers.QueueListener` now implements the context
manager protocol, allowing it to be used in a :keyword:`with` statement.
(Contributed by Charles Machalow in :gh:`132106`.)
mimetypes mimetypes
--------- ---------

View file

@ -1532,6 +1532,19 @@ class QueueListener(object):
self._thread = None self._thread = None
self.respect_handler_level = respect_handler_level self.respect_handler_level = respect_handler_level
def __enter__(self):
"""
For use as a context manager. Starts the listener.
"""
self.start()
return self
def __exit__(self, *args):
"""
For use as a context manager. Stops the listener.
"""
self.stop()
def dequeue(self, block): def dequeue(self, block):
""" """
Dequeue a record and return it, optionally blocking. Dequeue a record and return it, optionally blocking.

View file

@ -4311,8 +4311,6 @@ class QueueHandlerTest(BaseTest):
self.assertEqual(formatted_msg, log_record.msg) self.assertEqual(formatted_msg, log_record.msg)
self.assertEqual(formatted_msg, log_record.message) self.assertEqual(formatted_msg, log_record.message)
@unittest.skipUnless(hasattr(logging.handlers, 'QueueListener'),
'logging.handlers.QueueListener required for this test')
def test_queue_listener(self): def test_queue_listener(self):
handler = TestHandler(support.Matcher()) handler = TestHandler(support.Matcher())
listener = logging.handlers.QueueListener(self.queue, handler) listener = logging.handlers.QueueListener(self.queue, handler)
@ -4347,8 +4345,17 @@ class QueueHandlerTest(BaseTest):
self.assertTrue(handler.matches(levelno=logging.CRITICAL, message='6')) self.assertTrue(handler.matches(levelno=logging.CRITICAL, message='6'))
handler.close() handler.close()
@unittest.skipUnless(hasattr(logging.handlers, 'QueueListener'), def test_queue_listener_context_manager(self):
'logging.handlers.QueueListener required for this test') handler = TestHandler(support.Matcher())
with logging.handlers.QueueListener(self.queue, handler) as listener:
self.assertIsInstance(listener, logging.handlers.QueueListener)
self.assertIsNotNone(listener._thread)
self.assertIsNone(listener._thread)
# doesn't hurt to call stop() more than once.
listener.stop()
self.assertIsNone(listener._thread)
def test_queue_listener_with_StreamHandler(self): def test_queue_listener_with_StreamHandler(self):
# Test that traceback and stack-info only appends once (bpo-34334, bpo-46755). # Test that traceback and stack-info only appends once (bpo-34334, bpo-46755).
listener = logging.handlers.QueueListener(self.queue, self.root_hdlr) listener = logging.handlers.QueueListener(self.queue, self.root_hdlr)
@ -4363,8 +4370,6 @@ class QueueHandlerTest(BaseTest):
self.assertEqual(self.stream.getvalue().strip().count('Traceback'), 1) self.assertEqual(self.stream.getvalue().strip().count('Traceback'), 1)
self.assertEqual(self.stream.getvalue().strip().count('Stack'), 1) self.assertEqual(self.stream.getvalue().strip().count('Stack'), 1)
@unittest.skipUnless(hasattr(logging.handlers, 'QueueListener'),
'logging.handlers.QueueListener required for this test')
def test_queue_listener_with_multiple_handlers(self): def test_queue_listener_with_multiple_handlers(self):
# Test that queue handler format doesn't affect other handler formats (bpo-35726). # Test that queue handler format doesn't affect other handler formats (bpo-35726).
self.que_hdlr.setFormatter(self.root_formatter) self.que_hdlr.setFormatter(self.root_formatter)

View file

@ -0,0 +1,2 @@
:class:`logging.handlers.QueueListener` now implements the context
manager protocol, allowing it to be used in a :keyword:`with` statement.