【问题标题】:Adding stack info to logging format in python在 python 中将堆栈信息添加到日志记录格式
【发布时间】:2019-07-11 20:43:51
【问题描述】:

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

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

# 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 推动了正确的方向。

不幸的是,默认的Formatterformat 方法只是将formatStack 的输出附加到要打印的字符串的末尾。不管你做什么,只要你使用默认的format 实现就会发生什么。

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

相反,inspect.stack()(由新的formatStack 返回)的格式化结果被放置在将被格式化的LogRecordstack_info 成员中。

这样做是因为要获得将要打印的最终消息,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

【问题讨论】:

    标签: python python-3.x logging io


    【解决方案1】:

    最简单的方法是创建一个自定义的Formatter 子类,它会覆盖formatStack 方法,该方法记录在here 中。

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

    【讨论】:

    • 谢谢!我已经解决了我的问题!您描述的方式并没有提供直接这样做的可能性,但是它确实导致了另一个有趣的实现,它完全符合我的要求!请看一下!
    【解决方案2】:

    我无法发表评论,所以我正在使用答案:

    这是我以前使用的一个装饰器,它记录执行的函数及其参数

    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
    

    【讨论】:

    • 谢谢!但这根本不是我想做的!我需要记录函数是可调用的,而不是让装饰器记录函数调用。 (即我想在同一个函数中有多个日志/“打印”,我想知道是什么函数,谁调用了那个函数,谁调用了调用函数等等)
    猜你喜欢
    • 2015-02-16
    • 2020-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多