Создание нового регистратора для каждого вызова асинхронной функции, хорошая идея или нет? - PullRequest
0 голосов
/ 27 декабря 2018

При написании асинхронных программ на Python часто существует асинхронная функция, которая запускает много вызовов одновременно.Я хочу добавить некоторые записи в эту функцию, но выходные данные журнала из разных вызовов будут чередоваться, что усложнит отслеживание.Мое текущее решение состоит в том, чтобы каким-то образом создать уникальное имя для каждого вызова и регистрировать это имя каждый раз, например:

async def make_request(args):
    logger = logging.getLogger('myscript.request')
    log_name = unique_name()
    logger.debug('[%s] making request with args %r', log_name, args)
    response = await request(args)
    logger.debug('[%s] response: %r', log_name, response)

Однако необходимость вставлять log_name в каждый вызов регистрации утомляет довольно быстро.Чтобы сохранить эти нажатия клавиш, я придумал другое решение, создавая новый регистратор с уникальным именем для каждого вызова:

async def make_request(args):
    logger = logging.getLogger(f'myscript.request.{unique_name()}')
    logger.debug('making request with args %r', args)
    response = await request(args)
    logger.debug('response: %r', response)

Есть ли недостатки этого подхода?Единственное, о чем я могу думать, это то, что создание нового регистратора может быть дорогим, но так ли это на самом деле?Есть ли подводные камни, которых я не вижу?

Ответы [ 2 ]

0 голосов
/ 27 декабря 2018

Есть ли какие-либо недостатки в [создании нового регистратора для каждой сопрограммы]?

Кроме возможной цены создания регистратора, еще одним недостатком является то, что созданный вами регистратор остаетсяассоциируется с уникальным именем навсегда и никогда не уничтожается, поэтому у вас фактически возникает утечка памяти.Это явно обещано в документации:

Несколько вызовов getLogger() с одинаковым именем всегда будут возвращать ссылку на один и тот же объект Logger.

Я бырекомендуем просто прикусить пулю и создать помощника с желаемым функционалом.Основываясь на ответе Брэда Соломона, оболочка может выглядеть так (не проверено):

import itertools, weakref, logging

logging.basicConfig(format='%(asctime)-15s %(task_name)s %(message)s')

class TaskLogger:
    _next_id = itertools.count().__next__
    _task_ids = weakref.WeakKeyDictionary()

    def __init__(self):
        self._logger = logging.getLogger('myscript.request')

    def _task_name(self):
        task = asyncio.current_task()
        if task not in self._task_ids:
            self._task_ids[task] = self._next_id()
        return f'task-{self._task_ids[task]}'

    def debug(self, *args, **kwargs):
        self._logger.debug(*args, task_name=self._task_name(), **kwargs)

    # the same for info, etc.

logger = TaskLogger()
0 голосов
/ 27 декабря 2018

Вместо создания нового регистратора, вы можете рассмотреть возможность использования пользовательских атрибутов в сообщении журнала через параметр extra:

Например:

FORMAT = '%(asctime)-15s %(unique_name)s %(message)s'
# [Configure/format loggers & handlers]

Тогда в журнале вызовов сопрограмм сообщение об уровне отладки будет выглядеть примерно так:

logger.debug('making request with args %r', args, extra={'unique_name': unique_name())

Еще одна вещь, о которой следует помнить: unique_name() может дорого обойтись, если выДелаем много запросов.Распространенный шаблон при создании параллелизма через многопроцессорность заключается в регистрации идентификатора вызывающего процесса через os.getpid()asyncio, возможно, очень грубый двоюродный брат будет некоторым идентификатором для текущего Task, который вы можете получить через asyncio.current_task().Каждое задание имеет атрибут _name, который должен быть уникальным, поскольку он вызывает возрастающее значение _task_name_counter():

class Task(futures._PyFuture): # Inherit Python Task implementation
    def __init__(self, coro, *, loop=None, name=None):
    # ...
        if name is None:
            self._name = f'Task-{_task_name_counter()}'
        else:
            self._name = str(name)
...