Skip to content
13 changes: 13 additions & 0 deletions Doc/howto/logging-cookbook.rst
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,19 @@ which, when run, will produce:
of each message with the handler's level, and only passes a message to a
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:

Sending and receiving logging events across a network
Expand Down
7 changes: 7 additions & 0 deletions Doc/library/logging.handlers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,13 @@ possible, while any potentially slow operations (such as sending an email via
.. versionchanged:: 3.5
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)

Dequeues a record and return it, optionally blocking.
Expand Down
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,14 @@ linecache
(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
---------

Expand Down
13 changes: 13 additions & 0 deletions Lib/logging/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,19 @@ def __init__(self, queue, *handlers, respect_handler_level=False):
self._thread = None
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):
"""
Dequeue a record and return it, optionally blocking.
Expand Down
17 changes: 11 additions & 6 deletions Lib/test/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -4311,8 +4311,6 @@ def test_formatting(self):
self.assertEqual(formatted_msg, log_record.msg)
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):
handler = TestHandler(support.Matcher())
listener = logging.handlers.QueueListener(self.queue, handler)
Expand Down Expand Up @@ -4347,8 +4345,17 @@ def test_queue_listener(self):
self.assertTrue(handler.matches(levelno=logging.CRITICAL, message='6'))
handler.close()

@unittest.skipUnless(hasattr(logging.handlers, 'QueueListener'),
'logging.handlers.QueueListener required for this test')
def test_queue_listener_context_manager(self):
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):
# Test that traceback and stack-info only appends once (bpo-34334, bpo-46755).
listener = logging.handlers.QueueListener(self.queue, self.root_hdlr)
Expand All @@ -4363,8 +4370,6 @@ def test_queue_listener_with_StreamHandler(self):
self.assertEqual(self.stream.getvalue().strip().count('Traceback'), 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):
# Test that queue handler format doesn't affect other handler formats (bpo-35726).
self.que_hdlr.setFormatter(self.root_formatter)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:class:`logging.handlers.QueueListener` now implements the context
manager protocol, allowing it to be used in a :keyword:`with` statement.
Loading