diff --git a/Doc/howto/logging.rst b/Doc/howto/logging.rst index 93400627136e35..125bde29152270 100644 --- a/Doc/howto/logging.rst +++ b/Doc/howto/logging.rst @@ -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 diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index 3310c73656258f..ac86bc8077ef74 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -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: diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 79e4b7d09bfa43..e7636e18bec4ab 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -64,20 +64,25 @@ 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 @@ def __init__(self, name, level, pathname, lineno, 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 ''%(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 diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index fd562322a76372..07804a2686a140 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -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.logging_helper import TestHandler import textwrap import threading +import asyncio import time import unittest import warnings @@ -4552,29 +4552,63 @@ def test_multiprocessing(self): 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 @@ def dummy_handle_error(record): # 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 @@ def test__all__(self): '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 index 00000000000000..f9f93767ed173d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-05-25-00-21-28.gh-issue-91513.9VyCT4.rst @@ -0,0 +1 @@ +Added ``taskName`` attribute to :mod:`logging` module for use with :mod:`asyncio` tasks.