Подтвердить что ты не робот

Каков правильный способ фильтрации разных регистраторов с использованием протоколов python?

Моя цель - выполнять многомодульный журнал с иерархической фильтрацией

как это предлагается автором журнала Vinay Sajip, по крайней мере, насколько я предполагаю; -)

Вы можете перейти к Как я хочу, чтобы он работал "

К сожалению, я очень быстро узнал, что работа с системами ведения журнала намного сложнее, чем большинство других моих знаний с языком, и я уже сделал много общих (дизайнерских) ошибок, например. пытаясь добиться централизованного ведения журнала Logger для нескольких модулей или даже таких схем, как (используя класс журнала Python для генерации нескольких журналов для разных уровней журналов). Но, видимо, есть место для лучшего дизайна, и, возможно, это будет еще труднее найти время и изучить его. Итак, прямо сейчас я надеюсь, что я на правильном пути. В противном случае Вина должен будет прояснить остальное: -)

Я упорядочиваю свой журнал следующим образом:

  • Каждый модуль python имеет свой собственный журнал.
  • Каждый регистратор имеет имя, такое же, как и модуль, в котором он определен, например. logger = logging.getLogger(__name__)
  • Подобно этому, код внутри каждого модуля может использовать свой собственный (локально определенный) регистратор для отправки журнальных сообщений (logging.LogRecord) обработчикам (logging.Handler)
  • Используйте logging.config, чтобы получить полную гибкость при настройке ведения журнала (Примечание: в приведенном ниже коде я просто начинаю с basicConfig)

Такой подход является рекомендуемым методом, и я согласен с его возможными преимуществами. Например, я могу включить/выключить DEBUG внешних библиотек, используя полностью квалифицированные имена модулей (иерархия именования, которая уже существует в коде).

Теперь, чтобы иметь более высокий уровень контроля, я хочу использовать класс logging.Filter, чтобы иметь возможность фильтровать (разрешать) только выбранное поддерево в иерархии регистраторов.

Все в порядке, но фильтрация, описанная здесь

Filter instances are used to perform arbitrary filtering of LogRecords.

Loggers and Handlers can optionally use Filter instances to filter
records as desired. The base filter class only allows events which are
below a certain point in the logger hierarchy. For example, a filter
initialized with "A.B" will allow events logged by loggers "A.B",
"A.B.C", "A.B.C.D", "A.B.D" etc. but not "A.BB", "B.A.B" etc. If
initialized with the empty string, all events are passed.

все еще не работает для меня.

Мое предположение заключается в том, что я не понимаю, какие детали распространения LogRecords являются источником проблемы. Прежде чем перейти к коду, я хочу показать здесь блок-схему (из учебник поваренной книги, который сначала я как-то не сразу обнаружил): logging flow-chart

Пример кода

Я начинаю с двух примеров модулей, каждый из которых использует собственный именованный логгер:

bar.py:

import logging


logger = logging.getLogger(__name__)


def bar():
    logger.info('hello from ' + __name__)

foo.py:

import logging
from bar import bar, logger as bar_logger


logger = logging.getLogger('foo')


def foo():
    logger.info('hello from foo')


if __name__ == '__main__':
    # Trivial logging setup.
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s %(name)-20s %(levelname)-8s %(message)s',
        datefmt='%m-%d %H:%M'
    )
    # Do some work.
    foo()
    bar()

Ведение журнала сначала построено с помощью logging.basicConfig(корневой журнал, созданный после import logging на __main__, прикрепляет к нему обработчик потока, так что у нас есть консоль), (соответствующий Logger.disabled = False), и оба логгера модуля bar и foo распространяются на корневой журнал (так что мы имеем всего три регистратора).

print logger
print bar_logger
print logging.root
# Prints 
#<logging.Logger object at 0x7f0cfd520790>
#<logging.Logger object at 0x7f0cfd55d710>
#<logging.RootLogger object at 0x7f0cfd520550>

Фактическая Usecase - это когда bar - это внешняя библиотека, которую я хочу отключить (отфильтровать).

Как это работает, но "мне" это не нравится

# Don't like it
bar_logger.addFilter(logging.Filter('foo'))
# Do some work.
foo()
bar()

печатает только

06-24 14:08 foo                  INFO     hello from foo

Как я хочу, чтобы он работал

Я хочу отфильтровать его централизованно, т.е. в моем корневом журнале без необходимости импорта всех регистраторов всех внешних модулей.

logging.root.addFilter(logging.Filter('foo'))

печатает

06-24 14:17 foo                  INFO     hello from foo
06-24 14:17 bar                  INFO     hello from bar

Должна быть какая-то явная/глупая ошибка, которую я пропустил: я не хочу получать сообщения из журнала bar. Эй, но что это лучший способ найти его, чем суммировать все на SO, ребята?; -)

Я попытаюсь выяснить, как bar_logger ждет решения от корневого регистратора, прежде чем что-либо исправить. Я просто надеюсь, что это действительно то, как он должен работать в первую очередь.

4b9b3361

Ответ 1

Решение

Добавьте фильтр к обработчику, а не к регистратору:

handler.addFilter(logging.Filter('foo'))

Объяснение

В диаграмме блок-схемы, которую вы опубликовали, обратите внимание на наличие двух бриллиантов:

  • Отключает ли фильтр, прикрепленный к logger, запись?
  • Отключает ли фильтр, прикрепленный к hander, запись?

Таким образом, вы получаете два колебания при отказе от LogRecord. Если вы присоедините фильтр к корневому журналу, но инициируете LogRecord через, скажем, журналы foo или bar, тогда LogRecord не фильтруется, потому что LogRecord свободно проходит через foo или bar loggers, и фильтр корневого регистратора никогда не входит в играть. (Посмотрите на блок-схему снова.)

Напротив, StreamHandler, определяемый basicConfig, способен фильтровать любые пропуски LogRecord на него.

Итак: добавьте фильтр к обработчику, а не к регистратору:

# foo.py
import logging
import bar

logger = logging.getLogger('foo')

def foo():
    logger.info('hello from foo')

if __name__ == '__main__':
    # Trivial logging setup.
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s %(name)-20s %(levelname)-8s %(message)s',
        datefmt='%m-%d %H:%M')
    for handler in logging.root.handlers:
        handler.addFilter(logging.Filter('foo'))

    foo()
    bar.bar()

дает

06-24 09:17 foo                  INFO     hello from foo

Если вы хотите разрешить ведение журнала с помощью журналов, чье имя начинается с foo или bar, но не с каких-либо других регистраторов, вы можете создать белый список, например:

import logging
foo_logger = logging.getLogger('foo')
bar_logger = logging.getLogger('bar')
baz_logger = logging.getLogger('baz')

class Whitelist(logging.Filter):
    def __init__(self, *whitelist):
        self.whitelist = [logging.Filter(name) for name in whitelist]

    def filter(self, record):
        return any(f.filter(record) for f in self.whitelist)

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s %(name)-20s %(levelname)-8s %(message)s',
    datefmt='%m-%d %H:%M')
for handler in logging.root.handlers:
    handler.addFilter(Whitelist('foo', 'bar'))

foo_logger.info('hello from foo')
# 06-24 09:41 foo                  INFO     hello from foo
bar_logger.info('hello from bar')
# 06-24 09:41 bar                  INFO     hello from bar
baz_logger.info('hello from baz')
# No output since Whitelist filters if record.name not begin with 'foo' or 'bar'

И аналогичным образом вы могли бы черным списком имен журналов с этим:

class Blacklist(Whitelist):
    def filter(self, record):
        return not Whitelist.filter(self, record)