Регистрация в разных местах через имя модуля и серьезность - PullRequest
0 голосов
/ 08 января 2019

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

Вот пример структуры:

logtest/
    logtest/
        __init__.py
        a.py
        b.py
        ...
        e.py
        f.py
    LICENSE
    README
    .gitignore, etc

Я хотел бы использовать экземпляр logging.handlers.RotatingFileHandler для каждого модуля и каждого стандартного уровня серьезности , найденного в пакете регистрации.

Каждый файл .py в настоящее время содержит только logger = logging.getLogger(__name__); для a.py это даст logger.name == 'logtest.a'. Чего я хотел бы добиться, так это настроить несколько обработчиков для каждого регистратора:

  • Тот, который относится только к соответствующему модулю, то есть /tmp/mod-a.log. Уровень этого обработчика должен быть NOTSET.
  • Пять относятся к пяти уровням ведения журнала, кроме NOTSET, то есть к уровням с 10 по 50. Каждый из них имеет соответствующий .level для фильтрации только записей журнала этой важности, но источник может поступать из любого экземпляра средства ведения журнала.

Картина, вероятно, говорит гораздо больше:

logtest/a.py  -->  /tmp/a.log  # Any severity level
logtest/b.py  -->  /tmp/b.log
... 
logtest/e.py  -->  /tmp/e.log

logtest/a.py  __
logtest/b.py  __\___ `/tmp/level-critical.log` (if level is logging.CRITICAL)
logtest/e.py  __/

logtest/a.py  __
logtest/b.py  __\___ `/tmp/level-error.log` (if level is logging.ERROR)
logtest/e.py  __/

(Да, я понимаю, что это будет регистрировать сообщения с избыточностью.)

Каков рекомендуемый и эффективный способ проектирования, такой как настройка? Кажется, что logging.config.dictConfig было бы полезно, за исключением того, что он удаляется от вызовов logging.getLogger(__name__).


Если вышеприведенное немного неясно: у меня есть экземпляр logger для каждого модуля, кроме __init__.py, каждый из которых создается с помощью logging.getLogger(__name__). Я понимаю, что могу добавить 5 обработчиков файлов к корневому регистратору, потому что все остальные регистраторы наследуют его. Однако существует ли более программный способ добавления обработчика файлов «уровня модуля» для каждого регистратора?

Я знаю, что добавление обработчиков уровня серьезности можно сделать так:

_levels = ("debug", "info", "warning", "error", "critical")
_level_handlers = {
    level: {
        "class": "logging.handlers.RotatingFileHandler",
        "filename": "/tmp/level-{}.log".format(level),
        "maxBytes": 750000,
        "backupCount": 5,
        "level": level.upper()
    } for level in _levels
}

LOGGING = {
    "version": 1,
    "loggers": {
        # Root - all other logger.getLogger(__name__) instances are children
        "": {
            "handlers": _levels,
        }
    }
}

LOGGING['handlers'] = _level_handlers
logging.config.dictConfig(LOGGING)

Но я не уверен, как относиться ко второй части.

1 Ответ

0 голосов
/ 09 января 2019

Мое текущее решение состоит из двух частей (но, безусловно, оцените другие подходы):

  • Установите основанные на уровне обработчики в __init__ и добавьте их в родительский регистратор. Как указывает @Sraw, это должен быть __name__, а не корневой логгер, от которого могут наследоваться другие пакеты.
  • Добавьте вспомогательную функцию getModuleLogger(), которая имитирует getLogger() внутри __init__, но также добавляет туда обработчик файлов, специфичный для модуля.

Каждый модуль затем вызывает getModuleLogger(__name__) так же, как он обычно вызывает logging.getLogger(__name__), и никаких дополнительных настроек не требуется.

С __init__.py:

__all__ = ()

import logging
import logging.config
import logging.handlers
import os

_levels = ("debug", "info", "warning", "error", "critical")
_level_handlers = {
    level: {
        "class": "logging.handlers.RotatingFileHandler",
        "filename": "/tmp/level-{}.log".format(level),
        "maxBytes": 750_000,
        "backupCount": 5,
        "level": level.upper()
    } for level in _levels
}

LOGGING = {
    "version": 1,
    "loggers": {
        # Using the package name means modules in the package are nested
        # as children of this parent logger and will inherit its handlers
        __name__: {
            "handlers": _levels,
        }
    }
}

LOGGING['handlers'] = _level_handlers
logging.config.dictConfig(LOGGING)


def getModuleLogger(name):
    """Call this from modules instead of standard logging.getLogger()."""
    logger = logging.getLogger(name)
    logger.setLevel(logging.DEBUG)
    handler = logging.handlers.RotatingFileHandler(
        filename="/tmp/module-{}.log".format(name),
        maxBytes=750_000, backupCount=5
    )
    handler.setLevel(logging.DEBUG)
    logger.addHandler(handler)
    return logger

С a.py (аналогичная структура для других):

from logtest import getModuleLogger

logger = getModuleLogger(__name__)

def test_a():
    logger.info("info from a.py")
    logger.critical("critical from a.py")

Иллюстрация:

>>> from logtest import a, b
>>> a.test_a()
>>> b.test_b()
>>> from logtest.a import logger
>>> logger.handlers
[<RotatingFileHandler /tmp/module-logtest.a.log (DEBUG)>]
>>> from pprint import pprint
>>> pprint(logger.parent.handlers)
[<RotatingFileHandler /tmp/level-debug.log (DEBUG)>,
 <RotatingFileHandler /tmp/level-info.log (INFO)>,
 <RotatingFileHandler /tmp/level-warning.log (WARNING)>,
 <RotatingFileHandler /tmp/level-error.log (ERROR)>,
 <RotatingFileHandler /tmp/level-critical.log (CRITICAL)>]
>>> exit()

[logtest/] $ cat /tmp/module-logtest.a.log 
info from a.py
critical from a.py
[logtest/] $ cat /tmp/module-logtest.b.log 
info from b.py
critical from b.py
[logtest/] $ cat /tmp/level-critical.log 
critical from a.py
critical from b.py
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...