]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-91513: Add 'asyncio' taskName to logging LogRecord attributes. (GH-93193)
authorjackh-ncl <1750152+jackh-ncl@users.noreply.github.com>
Thu, 26 May 2022 08:30:51 +0000 (09:30 +0100)
committerGitHub <noreply@github.com>
Thu, 26 May 2022 08:30:51 +0000 (09:30 +0100)
Doc/howto/logging.rst
Doc/library/logging.rst
Lib/logging/__init__.py
Lib/test/test_logging.py
Misc/NEWS.d/next/Library/2022-05-25-00-21-28.gh-issue-91513.9VyCT4.rst [new file with mode: 0644]

index 93400627136e359501853c2e54ee9682d4e48c83..125bde291522703be2cf93ec2e70a784a6bb46bc 100644 (file)
@@ -1101,6 +1101,9 @@ need:
 | Current process name when using ``multiprocessing`` | Set ``logging.logMultiprocessing`` to ``False``.  |
 | to manage multiple processes.                       |                                                   |
 +-----------------------------------------------------+---------------------------------------------------+
+| Current :class:`asyncio.Task` name when using       | Set ``logging.logAsyncioTasks`` to ``False``.     |
+| ``asyncio``.                                        |                                                   |
++-----------------------------------------------------+---------------------------------------------------+
 
 Also note that the core logging module only includes the basic handlers. If
 you don't import :mod:`logging.handlers` and :mod:`logging.config`, they won't
index 3310c73656258f5323392be8f0338edc17f09dda..ac86bc8077ef74b93a810105571c867d3730f31c 100644 (file)
@@ -872,10 +872,14 @@ the options available to you.
 +----------------+-------------------------+-----------------------------------------------+
 | threadName     | ``%(threadName)s``      | Thread name (if available).                   |
 +----------------+-------------------------+-----------------------------------------------+
+| taskName       | ``%(taskName)s``        | :class:`asyncio.Task` name (if available).    |
++----------------+-------------------------+-----------------------------------------------+
 
 .. versionchanged:: 3.1
    *processName* was added.
 
+.. versionchanged:: 3.12
+   *taskName* was added.
 
 .. _logger-adapter:
 
index 79e4b7d09bfa43c43abcda8719c4f0a4d903d2fa..e7636e18bec4abfd436e9b545b6eaff3effbb0c6 100644 (file)
@@ -64,20 +64,25 @@ _startTime = time.time()
 raiseExceptions = True
 
 #
-# If you don't want threading information in the log, set this to zero
+# If you don't want threading information in the log, set this to False
 #
 logThreads = True
 
 #
-# If you don't want multiprocessing information in the log, set this to zero
+# If you don't want multiprocessing information in the log, set this to False
 #
 logMultiprocessing = True
 
 #
-# If you don't want process information in the log, set this to zero
+# If you don't want process information in the log, set this to False
 #
 logProcesses = True
 
+#
+# If you don't want asyncio task information in the log, set this to False
+#
+logAsyncioTasks = True
+
 #---------------------------------------------------------------------------
 #   Level related stuff
 #---------------------------------------------------------------------------
@@ -361,6 +366,15 @@ class LogRecord(object):
         else:
             self.process = None
 
+        self.taskName = None
+        if logAsyncioTasks:
+            asyncio = sys.modules.get('asyncio')
+            if asyncio:
+                try:
+                    self.taskName = asyncio.current_task().get_name()
+                except Exception:
+                    pass
+
     def __repr__(self):
         return '<LogRecord: %s, %s, %s, %s, "%s">'%(self.name, self.levelno,
             self.pathname, self.lineno, self.msg)
@@ -566,6 +580,7 @@ class Formatter(object):
                         (typically at application startup time)
     %(thread)d          Thread ID (if available)
     %(threadName)s      Thread name (if available)
+    %(taskName)s        Task name (if available)
     %(process)d         Process ID (if available)
     %(message)s         The result of record.getMessage(), computed just as
                         the record is emitted
index fd562322a7637299d6bbb099c66b53fb4904df7d..07804a2686a14085b8bf39fb5bc3b4eb2142d340 100644 (file)
@@ -18,7 +18,6 @@
 
 Copyright (C) 2001-2021 Vinay Sajip. All Rights Reserved.
 """
-
 import logging
 import logging.handlers
 import logging.config
@@ -50,6 +49,7 @@ from test.support import warnings_helper
 from test.support.logging_helper import TestHandler
 import textwrap
 import threading
+import asyncio
 import time
 import unittest
 import warnings
@@ -4552,29 +4552,63 @@ class LogRecordTest(BaseTest):
                 import multiprocessing
 
     def test_optional(self):
-        r = logging.makeLogRecord({})
+        NONE = self.assertIsNone
         NOT_NONE = self.assertIsNotNone
+
+        r = logging.makeLogRecord({})
         NOT_NONE(r.thread)
         NOT_NONE(r.threadName)
         NOT_NONE(r.process)
         NOT_NONE(r.processName)
+        NONE(r.taskName)
         log_threads = logging.logThreads
         log_processes = logging.logProcesses
         log_multiprocessing = logging.logMultiprocessing
+        log_asyncio_tasks = logging.logAsyncioTasks
         try:
             logging.logThreads = False
             logging.logProcesses = False
             logging.logMultiprocessing = False
+            logging.logAsyncioTasks = False
             r = logging.makeLogRecord({})
-            NONE = self.assertIsNone
+
             NONE(r.thread)
             NONE(r.threadName)
             NONE(r.process)
             NONE(r.processName)
+            NONE(r.taskName)
         finally:
             logging.logThreads = log_threads
             logging.logProcesses = log_processes
             logging.logMultiprocessing = log_multiprocessing
+            logging.logAsyncioTasks = log_asyncio_tasks
+
+    async def _make_record_async(self, assertion):
+        r = logging.makeLogRecord({})
+        assertion(r.taskName)
+
+    def test_taskName_with_asyncio_imported(self):
+        try:
+            make_record = self._make_record_async
+            with asyncio.Runner() as runner:
+                logging.logAsyncioTasks = True
+                runner.run(make_record(self.assertIsNotNone))
+                logging.logAsyncioTasks = False
+                runner.run(make_record(self.assertIsNone))
+        finally:
+            asyncio.set_event_loop_policy(None)
+
+    def test_taskName_without_asyncio_imported(self):
+        try:
+            make_record = self._make_record_async
+            with asyncio.Runner() as runner, support.swap_item(sys.modules, 'asyncio', None):
+                logging.logAsyncioTasks = True
+                runner.run(make_record(self.assertIsNone))
+                logging.logAsyncioTasks = False
+                runner.run(make_record(self.assertIsNone))
+        finally:
+            asyncio.set_event_loop_policy(None)
+
 
 class BasicConfigTest(unittest.TestCase):
 
@@ -4853,6 +4887,30 @@ class BasicConfigTest(unittest.TestCase):
             # didn't write anything due to the encoding error
             self.assertEqual(data, r'')
 
+    def test_log_taskName(self):
+        async def log_record():
+            logging.warning('hello world')
+
+        try:
+            encoding = 'utf-8'
+            logging.basicConfig(filename='test.log', errors='strict', encoding=encoding,
+                                format='%(taskName)s - %(message)s', level=logging.WARNING)
+
+            self.assertEqual(len(logging.root.handlers), 1)
+            handler = logging.root.handlers[0]
+            self.assertIsInstance(handler, logging.FileHandler)
+
+            with asyncio.Runner(debug=True) as runner:
+                logging.logAsyncioTasks = True
+                runner.run(log_record())
+        finally:
+            asyncio.set_event_loop_policy(None)
+            handler.close()
+            with open('test.log', encoding='utf-8') as f:
+                data = f.read().strip()
+            os.remove('test.log')
+            self.assertRegex(data, r'Task-\d+ - hello world')
+
 
     def _test_log(self, method, level=None):
         # logging.root has no handlers so basicConfig should be called
@@ -5644,7 +5702,7 @@ class MiscTestCase(unittest.TestCase):
             'logThreads', 'logMultiprocessing', 'logProcesses', 'currentframe',
             'PercentStyle', 'StrFormatStyle', 'StringTemplateStyle',
             'Filterer', 'PlaceHolder', 'Manager', 'RootLogger', 'root',
-            'threading'}
+            'threading', 'logAsyncioTasks'}
         support.check__all__(self, logging, not_exported=not_exported)
 
 
diff --git a/Misc/NEWS.d/next/Library/2022-05-25-00-21-28.gh-issue-91513.9VyCT4.rst b/Misc/NEWS.d/next/Library/2022-05-25-00-21-28.gh-issue-91513.9VyCT4.rst
new file mode 100644 (file)
index 0000000..f9f9376
--- /dev/null
@@ -0,0 +1 @@
+Added ``taskName`` attribute to :mod:`logging` module for use with :mod:`asyncio` tasks.