Skip to content

Commit

Permalink
gh-91513: Add 'asyncio' taskName to logging LogRecord attributes. (GH…
Browse files Browse the repository at this point in the history
  • Loading branch information
jackh-ncl authored May 26, 2022
1 parent 5185956 commit cc37706
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 7 deletions.
3 changes: 3 additions & 0 deletions Doc/howto/logging.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions Doc/library/logging.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
21 changes: 18 additions & 3 deletions Lib/logging/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
#---------------------------------------------------------------------------
Expand Down Expand Up @@ -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 '<LogRecord: %s, %s, %s, %s, "%s">'%(self.name, self.levelno,
self.pathname, self.lineno, self.msg)
Expand Down Expand Up @@ -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
Expand Down
66 changes: 62 additions & 4 deletions Lib/test/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
Copyright (C) 2001-2021 Vinay Sajip. All Rights Reserved.
"""

import logging
import logging.handlers
import logging.config
Expand Down Expand Up @@ -50,6 +49,7 @@
from test.support.logging_helper import TestHandler
import textwrap
import threading
import asyncio
import time
import unittest
import warnings
Expand Down Expand Up @@ -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):

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added ``taskName`` attribute to :mod:`logging` module for use with :mod:`asyncio` tasks.

0 comments on commit cc37706

Please sign in to comment.