Как определить логгер в Python один раз для всей программы?
Я хочу настроить свой логгер один раз в моем проекте Python и использовать его на протяжении всего проекта.
main.py
:
import logging
import test
hostname = {"hostname": socket.gethostname()}
logger = logging.getLogger()
syslog = logging.StreamHandler()
formatter = logging.Formatter("{\"label\":\"%(name)s\", \"level\":\"%(levelname)s\", \"hostname\":\"%(hostname)s\", \"logEntry\": %(message)s, \"timestamp\", \"%(asctime)s\"}")
syslog.setFormatter(formatter)
logger.setLevel(logging.DEBUG)
logger.addHandler(syslog)
logger = logging.LoggerAdapter(logger, hostname)
def entry_point():
logger.debug("entry_point")
test.test_function()
entry_point()
test.py
:
import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
def test_function():
logger.debug("test_function")
Это должно дать мне:
entry_point
test_function
... оба отформатированы с помощью поставщика форматов.
Однако я на самом деле получаю ошибку KeyError: 'hostname'
потому что, казалось бы, второй регистратор не знает о hostname
формат провайдера. Я также попытался инициализировать оба регистратора с __name__
но потом я получаю No handlers could be found for logger "test"
,
Есть ли способ, которым я могу определить свою конфигурацию регистрации один раз и повторно использовать ее в моем приложении?
1 ответ
LoggingAdapter
это отдельный объект, он не заменяет результат logging.getLogger()
звонки. Вам нужно будет хранить его где-нибудь, чтобы его можно было разделить между разными модулями. Например, вы можете использовать отдельный модуль, из которого импортируется все остальное в вашем проекте.
Ниже я подробно расскажу, как с этим справиться, но есть и альтернатива, которая вообще не использует адаптеры, а вместо этого использует фильтр, который присоединен к созданному вами обработчику, что позволяет вам вообще не иметь дело с адаптерами. Смотрите дальше вниз.
Я бы также выделил конфигурацию и обработку объектов журнала; Пусть основной модуль вызовет функцию "setup" для настройки обработчиков и уровней журнала, а также настроит адаптер в модуле, чтобы другие могли импортировать:
log.py
:
import logging
import socket
def setup_logging():
"""Adds a configured stream handler to the root logger"""
syslog = logging.StreamHandler()
formatter = logging.Formatter(
'{"label":"%(name)s", "level":"%(levelname)s",'
' "hostname":"%(hostname)s", "logEntry": %(message)s,'
' "timestamp", "%(asctime)s"}')
syslog.setFormatter(formatter)
logger = logging.getLogger()
logger.addHandler(syslog)
logger.setLevel(logging.DEBUG)
def host_log_adapter(logger):
hostname = {"hostname": socket.gethostname()}
return logging.LoggerAdapter(logger, hostname)
logger = host_log_adapter(logging.getLogger())
Затем в main
делать:
import log
import test
log.setup_logging()
def entry_point():
log.logger.debug("entry_point")
test.test_function()
entry_point()
И в test
:
from log import logger
def test_function():
logger.debug("test_function")
В качестве альтернативы, вместо использования адаптера, вы можете добавить информацию в записи журнала с помощью (ab), используя фильтр:
class HostnameInjectingFilter(logging.Filter):
def __init__(self):
self.hostname = socket.gethostname()}
def filter(self, record):
record.hostname = self.hostname
return True
Это добавляет дополнительное поле непосредственно к объекту записи и всегда возвращает True
, Затем просто добавьте этот фильтр в обработчик, который должен иметь доступное имя хоста:
syslog = logging.StreamHandler()
formatter = logging.Formatter(
'{"label":"%(name)s", "level":"%(levelname)s",'
' "hostname":"%(hostname)s", "logEntry": %(message)s,'
' "timestamp", "%(asctime)s"}')
syslog.setFormatter(formatter)
syslog.setFilter(HostnameInjectingFilter())
Это избавляет от необходимости использовать адаптер полностью, так что вы можете удалить host_log_adapter()
функционировать целиком и просто использовать logging.getLogger()
везде.