在python中将堆栈信息添加到日志记录格式

时间:2019-02-18 12:50:08

标签: python python-3.x logging io

有没有办法在记录器的实际格式部分中使用可变数据?

我希望我的日志包含堆栈中函数的名称。例如,下面是以下代码:

# 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的{​​{1}}方法仅将format的输出附加在要打印的字符串的末尾。无论您做什么,只要使用默认的formatStack实现,就会发生这种情况。

我所做的是扩展format类,重新实现了Formatter方法。格式化方法的内容是从format模块复制并粘贴的,占70%,但是删除了附加堆栈信息的部分。

相反,将logging格式化好的结果(由新的inspect.stack()返回)放置在将要格式化的formatStack的{​​{1}}成员中。

之所以这样做,是因为要获得将要打印的最终消息,将stack_info的词典用作LogRecord的第二个操作数。因此,当格式字符串与LogRecord的字典进行插值时,fmt_string % args将包含格式正确的调用堆栈,因此真正要做的就是在其格式字符串中使用LogRecord

基本上,我已经在格式化程序的格式字符串中添加了对LogRecord.stack_info的支持。

代码:

%(stack_info)s

2 个答案:

答案 0 :(得分:1)

最简单的方法是拥有一个自定义的Formatter子类,该子类将覆盖here中记录的formatStack方法。

  

将指定的堆栈信息(由traceback.print_stack()返回的字符串,但删除最后一个换行符)格式化为字符串。此默认实现只是返回输入值。

答案 1 :(得分:-1)

我无法发表评论,所以我使用的是答案:

这是我过去使用的装饰器,它记录执行函数及其args

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