Как переопределить класс Logger с методом для выбора уровня журнала и строки вывода журнала
Мне нужно записывать журналы во многих местах моего приложения Django, и я хочу, чтобы они были стандартизированы, чтобы, когда люди вносят свой вклад, им не нужно было сильно беспокоиться об этом.
Так что вместо вызова:
logger.info(<standardized message here>)
Я хотел бы написать функцию или, возможно, класс, который наследует Logger
и выбирает правильные .info
, .warning
, .error
и так далее и выводит сообщение так, как мы хотим.
Так что в коде я бы вызвал только
log(request, 200, "info", "Success message")
А функция, которую я написал, выглядит так:
logger = logging.getLogger(__name__)
def log(request=None, status_code=None, level=None, message=None):
"""
Create a log stream in a standardized format
<request_path> <request_method> <status_code>: user <user_id> <message>
"""
method = request.method
path = request.path
user_id = None
if not request.user.is_anonymous:
user_id = request.user.id
log = f"{path} {method} {status_code}: user {user_id} {message}"
if level == 'info':
logger_type = logger.info
elif level == 'warning':
logger_type = logger.warning
elif level == 'error':
logger_type = logger.error
elif level == 'debug':
logger_type = logger.debug
return logger_type(log)
Проблема в том, что наш форматер журнала также записывает строку в коде, где произошел лог, а поскольку лог вызывается в функции log
, строка отражает return logger_type(log)
вместо фактической строки в коде, где был вызван log
.
Как написать класс с методом для автоматического выбора уровня журнала на основе входных данных и при этом вести журнал с той строки, где это произошло?
Я хочу иметь его в стандартизированном виде
Если вы действительно хотите делать вещи стандартным образом, то используйте то, что дает вам модуль logging
и не создавайте свои собственные вспомогательные функции.
В протоколировании уже можно передавать "дополнительные" аргументы для сообщения протоколирования (doc):
logger.info("success message", extra={"request": request, "status_code": 200})
Затем вы можете поместить свои логические части запроса в объект formatter.
Помимо соответствия стандарту, это даст вам дополнительную гибкость, поскольку ваш форматтер может делать различные вещи в зависимости от того, какие дополнительные параметры он получает.
В модуле протоколирования Python отсутствует то, что есть у других протоколирующих модулей, - это идея потоково-локального объекта "контекст". Вы могли бы использовать такой объект для сохранения запроса в момент его обработки, чтобы он был доступен во всех сообщениях журнала без явной передачи. Вы можете реализовать это, добавив словарь request_context
в текущий поток, и искать его в вашем форматере.
Если вы хотите придерживаться подхода вспомогательной функции, методы протоколирования принимают kwarg stacklevel
, который определяет соответствующее количество кадров стека, которые следует пропустить при вычислении номера строки и имени функции ref. По умолчанию используется значение 1, поэтому при установке значения 2 следует искать фрейм над вашей вспомогательной функцией. Например:
import logging
logging.basicConfig(format="%(lineno)d")
logger = logging.getLogger(__name__)
def log():
logger.error("message", stacklevel=2)
log()