Skip to content
14 changes: 6 additions & 8 deletions Doc/howto/logging-cookbook.rst
Original file line number Diff line number Diff line change
Expand Up @@ -589,18 +589,16 @@ An example of using these two classes follows (imports omitted)::
que = queue.Queue(-1) # no limit on size
queue_handler = QueueHandler(que)
handler = logging.StreamHandler()
listener = QueueListener(que, handler)
root = logging.getLogger()
root.addHandler(queue_handler)
formatter = logging.Formatter('%(threadName)s: %(message)s')
handler.setFormatter(formatter)
listener.start()
# The log output will display the thread which generated
# the event (the main thread) rather than the internal
# thread which monitors the internal queue. This is what
# you want to happen.
root.warning('Look out!')
listener.stop()
with QueueListener(que, handler) as listener:
# The log output will display the thread which generated
# the event (the main thread) rather than the internal
# thread which monitors the internal queue. This is what
# you want to happen.
root.warning('Look out!')

which, when run, will produce:

Expand Down
11 changes: 11 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 Expand Up @@ -1179,6 +1186,10 @@ possible, while any potentially slow operations (such as sending an email via
This starts up a background thread to monitor the queue for
LogRecords to process.

.. versionchanged:: next
Raises :exc:`RuntimeError` if called and the listener is already
running.

.. method:: stop()

Stops the listener.
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
15 changes: 15 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 All @@ -1548,6 +1561,8 @@ def start(self):
This starts up a background thread to monitor the queue for
LogRecords to process.
"""
if self._thread is not None:
raise RuntimeError("Listener already started")
self._thread = t = threading.Thread(target=self._monitor)
t.daemon = True
t.start()
Expand Down
26 changes: 26 additions & 0 deletions Lib/test/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -4347,6 +4347,32 @@ 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)

@unittest.skipUnless(hasattr(logging.handlers, 'QueueListener'),
'logging.handlers.QueueListener required for this test')
def test_queue_listener_multi_start(self):
handler = TestHandler(support.Matcher())
with logging.handlers.QueueListener(self.queue, handler) as listener:
self.assertRaises(RuntimeError, listener.start)

with listener:
self.assertRaises(RuntimeError, listener.start)

listener.start()
listener.stop()

@unittest.skipUnless(hasattr(logging.handlers, 'QueueListener'),
'logging.handlers.QueueListener required for this test')
def test_queue_listener_with_StreamHandler(self):
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