Добавление информации стека в формат регистрации в python - PullRequest
0 голосов
/ 18 февраля 2019

Есть ли способ использовать переменные данные в фактической части формата регистратора?

Я хочу, чтобы мои журналы содержали имена функций в стеке.Вот, например, следующий код:

# logging_utils.py

def init_logger(logger_name: str) -> logging.Logger:

    log = logging.getLogger(logger_name)
    log.setLevel(logging.INFO)

    handler = logging.StreamHandler(sys.stdout)
    handler.setLevel(logging.INFO)

    formatter = logging.Formatter(f'[ %(asctime)s ] [ %(levelname)s ] [ %(callStack?)s ] %(message)s')

    handler.setFormatter(formatter)

    log.addHandler(handler)

    return log


def format_stack() -> str:
    return ':'.join(frame.function for frame in inspect.stack()[::-1][:-1]).replace('<module>:', '')



def f():
    g()

def g():
    h()

def h():
    logger = init_logger('x')
    logger.info('My actual message')


if __name__ == '__main__':
    f()

Я бы хотел, чтобы сообщения регистрации выглядели так:

[ 2019-02-18 14:14:23,558 ] [ INFO ] [ logging_utils:f:g:h ] My actual message

Ближайшее, к чему я пришел, - это использование собственного класса, подобного этому:

import sys
import inspect
import logging


class Logger:

    _logger: logging.Logger

    def __init__(self, name: str):

        logger = logging.getLogger(name)
        logger.setLevel(logging.INFO)

        handler = logging.StreamHandler(sys.stdout)
        handler.setLevel(logging.INFO)

        formatter = logging.Formatter(f'[ %(asctime)s ] [ %(levelname)s ] %(message)s')

        handler.setFormatter(formatter)

        logger.addHandler(handler)

        self._logger = logger

    @staticmethod
    def _format_stack_for_logger() -> str:

        stack = inspect.stack()[::-1]
        stack_names = (inspect.getmodulename(stack[0].filename),
                       *(frame.function
                         for frame
                         in stack[1:-3]))

        return '::'.join(stack_names)

    def _log(self, level: int, msg: str, *args, **kwargs):
        self._logger.log(level, '[ %s ] %s', self._format_stack_for_logger(), msg, *args, **kwargs)

    def debug(self, msg: str, *args, **kwargs):
        self._log(logging.DEBUG, msg, *args, **kwargs)

    def info(self, msg: str, *args, **kwargs):
        self._log(logging.INFO, msg, *args, **kwargs)

    def warning(self, msg: str, *args, **kwargs):
        self.info(logging.WARNING, msg, *args, **kwargs)

    def error(self, msg: str, *args, **kwargs):
        self._log(logging.ERROR, msg, *args, **kwargs)

    def critical(self, msg: str, *args, **kwargs):
        self._log(logging.CRITICAL, msg, *args, **kwargs)


def f():
    g()


def g():
    h()


def h():
    logger = Logger('x')
    logger.info('My actual message :(')


if __name__ == '__main__':
    f()

Но информация трассировки стека ... полужестко закодирована (?) В строку регистрации.Я ищу элегантный способ получить этот результат.

Заранее спасибо!

РЕДАКТИРОВАТЬ:

Благодаря @VinaySajip для подталкивания вправильное направление.

К сожалению, метод Formatter по умолчанию *1022* просто добавляет вывод formatStack в конце строки для печати.Независимо от того, что вы делаете, если вы используете реализацию по умолчанию format, это то, что произойдет.

Что я сделал, так это расширил класс Formatter, переопределив метод format.Содержимое метода форматирования копируется на 70% и вставляется из модуля logging, но часть, которая добавляет информацию стека, удаляется.

Вместо этого красиво отформатированный результат inspect.stack() (возвращаемый новым formatStack) помещается в stack_info член LogRecord, который будет отформатирован.

Это сделано потому, что для получения окончательного сообщения, которое будет напечатано, словарь LogRecord используется в качестве второго операнда fmt_string % args.Поэтому, когда строка формата интерполируется со словарем LogRecord, LogRecord.stack_info будет содержать красиво отформатированный стек вызовов, поэтому все, что действительно нужно сделать, это использовать %(stack_info)s в их строке формата.

По существу, ядобавлена ​​поддержка %(stack_info)s в строке форматирования форматера.

Код:

import sys
import inspect
import logging

from logging import Logger


class CallStackFormatter(logging.Formatter):

    def formatStack(self, _ = None) -> str:
        stack = inspect.stack()[::-1]
        stack_names = (inspect.getmodulename(stack[0].filename),
                       *(frame.function
                         for frame
                         in stack[1:-9]))
        return '::'.join(stack_names)

    def format(self, record):
        record.message = record.getMessage()
        record.stack_info = self.formatStack()
        if self.usesTime():
            record.asctime = self.formatTime(record, self.datefmt)
        s = self.formatMessage(record)
        if record.exc_info:
            # Cache the traceback text to avoid converting it multiple times
            # (it's constant anyway)
            if not record.exc_text:
                record.exc_text = self.formatException(record.exc_info)
        if record.exc_text:
            if s[-1:] != "\n":
                s = s + "\n"
            s = s + record.exc_text
        return s


def get_logger(name: str, fmt: str = '[ %(asctime)s ] [ %(levelname)-5s ] [ %(stack_info)s ] %(message)s') -> Logger:

        logger = logging.getLogger(name)
        logger.setLevel(logging.INFO)

        handler = logging.StreamHandler(sys.stdout)
        handler.setLevel(logging.INFO)

        formatter = CallStackFormatter(fmt)

        handler.setFormatter(formatter)

        logger.addHandler(handler)

        return logger

Ответы [ 2 ]

0 голосов
/ 18 февраля 2019

Я не могу комментировать, поэтому я использую ответ:

Вот декоратор, который я использовал, который записывает выполненную функцию с ее аргументами

def logging(f):
@wraps(f)
def wrapper(*args, **kwargs):
    import logging
    now = datetime.datetime.now()
    time = now.strftime("%Y-%m-%d %H:%M")
    logging.basicConfig(filename='logs/webservices.log',level=logging.INFO)
    handle = f(*args, **kwargs)
    logging.info(f'[{time}] - Executing instruction : [{" ".join((inspect.stack()[1][4][0]).split())}]')
    logging.info(f'[{time}] - with args [{args}]')
    logging.info(f'[{time}] - Returned result [{handle}]')
    return handle
return wrapper
0 голосов
/ 18 февраля 2019

Самый простой способ - создать собственный подкласс Formatter, который переопределяет метод formatStack, который задокументирован здесь .

Форматирует указанную информацию стека (aстрока, возвращаемая traceback.print_stack(), но с последним удаленным символом новой строки) в виде строки.Эта реализация по умолчанию просто возвращает входное значение.

...