Skip to content

Commit 517e96b

Browse files
authored
gh-132106: Allow logging.handlers.QueueListener to be used as a context manager (#132107)
1 parent ad3bbe8 commit 517e96b

File tree

6 files changed

+54
-6
lines changed

6 files changed

+54
-6
lines changed

Doc/howto/logging-cookbook.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,19 @@ which, when run, will produce:
626626
of each message with the handler's level, and only passes a message to a
627627
handler if it's appropriate to do so.
628628

629+
.. versionchanged:: next
630+
The :class:`QueueListener` can be started (and stopped) via the
631+
:keyword:`with` statement. For example:
632+
633+
.. code-block:: python
634+
635+
with QueueListener(que, handler) as listener:
636+
# The queue listener automatically starts
637+
# when the 'with' block is entered.
638+
pass
639+
# The queue listener automatically stops once
640+
# the 'with' block is exited.
641+
629642
.. _network-logging:
630643

631644
Sending and receiving logging events across a network

Doc/library/logging.handlers.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,6 +1148,13 @@ possible, while any potentially slow operations (such as sending an email via
11481148
.. versionchanged:: 3.5
11491149
The ``respect_handler_level`` argument was added.
11501150

1151+
.. versionchanged:: next
1152+
:class:`QueueListener` can now be used as a context manager via
1153+
:keyword:`with`. When entering the context, the listener is started. When
1154+
exiting the context, the listener is stopped.
1155+
:meth:`~contextmanager.__enter__` returns the
1156+
:class:`QueueListener` object.
1157+
11511158
.. method:: dequeue(block)
11521159

11531160
Dequeues a record and return it, optionally blocking.

Doc/whatsnew/3.14.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,14 @@ linecache
812812
(Contributed by Tian Gao in :gh:`131638`.)
813813

814814

815+
logging.handlers
816+
----------------
817+
818+
* :class:`logging.handlers.QueueListener` now implements the context
819+
manager protocol, allowing it to be used in a :keyword:`with` statement.
820+
(Contributed by Charles Machalow in :gh:`132106`.)
821+
822+
815823
mimetypes
816824
---------
817825

Lib/logging/handlers.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1532,6 +1532,19 @@ def __init__(self, queue, *handlers, respect_handler_level=False):
15321532
self._thread = None
15331533
self.respect_handler_level = respect_handler_level
15341534

1535+
def __enter__(self):
1536+
"""
1537+
For use as a context manager. Starts the listener.
1538+
"""
1539+
self.start()
1540+
return self
1541+
1542+
def __exit__(self, *args):
1543+
"""
1544+
For use as a context manager. Stops the listener.
1545+
"""
1546+
self.stop()
1547+
15351548
def dequeue(self, block):
15361549
"""
15371550
Dequeue a record and return it, optionally blocking.

Lib/test/test_logging.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4311,8 +4311,6 @@ def test_formatting(self):
43114311
self.assertEqual(formatted_msg, log_record.msg)
43124312
self.assertEqual(formatted_msg, log_record.message)
43134313

4314-
@unittest.skipUnless(hasattr(logging.handlers, 'QueueListener'),
4315-
'logging.handlers.QueueListener required for this test')
43164314
def test_queue_listener(self):
43174315
handler = TestHandler(support.Matcher())
43184316
listener = logging.handlers.QueueListener(self.queue, handler)
@@ -4347,8 +4345,17 @@ def test_queue_listener(self):
43474345
self.assertTrue(handler.matches(levelno=logging.CRITICAL, message='6'))
43484346
handler.close()
43494347

4350-
@unittest.skipUnless(hasattr(logging.handlers, 'QueueListener'),
4351-
'logging.handlers.QueueListener required for this test')
4348+
def test_queue_listener_context_manager(self):
4349+
handler = TestHandler(support.Matcher())
4350+
with logging.handlers.QueueListener(self.queue, handler) as listener:
4351+
self.assertIsInstance(listener, logging.handlers.QueueListener)
4352+
self.assertIsNotNone(listener._thread)
4353+
self.assertIsNone(listener._thread)
4354+
4355+
# doesn't hurt to call stop() more than once.
4356+
listener.stop()
4357+
self.assertIsNone(listener._thread)
4358+
43524359
def test_queue_listener_with_StreamHandler(self):
43534360
# Test that traceback and stack-info only appends once (bpo-34334, bpo-46755).
43544361
listener = logging.handlers.QueueListener(self.queue, self.root_hdlr)
@@ -4363,8 +4370,6 @@ def test_queue_listener_with_StreamHandler(self):
43634370
self.assertEqual(self.stream.getvalue().strip().count('Traceback'), 1)
43644371
self.assertEqual(self.stream.getvalue().strip().count('Stack'), 1)
43654372

4366-
@unittest.skipUnless(hasattr(logging.handlers, 'QueueListener'),
4367-
'logging.handlers.QueueListener required for this test')
43684373
def test_queue_listener_with_multiple_handlers(self):
43694374
# Test that queue handler format doesn't affect other handler formats (bpo-35726).
43704375
self.que_hdlr.setFormatter(self.root_formatter)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:class:`logging.handlers.QueueListener` now implements the context
2+
manager protocol, allowing it to be used in a :keyword:`with` statement.

0 commit comments

Comments
 (0)