Как переопределить класс 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()
Вернуться на верх