【问题标题】:How to change the format of logged messages temporarily, in Python?如何在 Python 中临时更改记录消息的格式?
【发布时间】:2011-10-14 10:35:04
【问题描述】:

在 Python 中(通过日志模块)临时更改日志消息格式的最简单方法是什么?

目标是拥有一些标准的消息格式,同时能够临时添加有关正在读取的某些文件的信息(如其名称);当不再读取文件时,消息格式应恢复为其默认值。生成消息的程序知道正在读取什么文件,所以如果它的消息自动包含相关文件名会很好(错误消息是:“ERROR while正在读取文件 ***: ...”而不是“ERROR: ...”)。

【问题讨论】:

    标签: python logging error-logging


    【解决方案1】:

    这里有一个简单的解决方案,可以从Vinay Sajip自己的HOWTO推导出来;它基本上用setFormatter()更新了日志格式化程序:

    import logging
    
    logger = logging.getLogger()  # Logger
    logger_handler = logging.StreamHandler()  # Handler for the logger
    logger.addHandler(logger_handler)
    
    # First, generic formatter:
    logger_handler.setFormatter(logging.Formatter('%(message)s'))
    logger.error('error message')  # Test
    
    # New formatter for the handler:
    logger_handler.setFormatter(logging.Formatter('PROCESSING FILE xxx - %(message)s'))
    logger.error('error message')  # Test
    

    这正确产生:

    error message
    PROCESSING FILE xxx - error message
    

    (其中xxx 可以动态设置为正在处理的文件,如问题中所要求的那样)。

    【讨论】:

    • 不确定为什么这被认为是一种解决方案。处理程序确定日志目的地,格式化程序确定日志格式;这两个可以被认为是正交的。如果我想临时更改特定记录器(例如记录器package1.module2)的日志格式以添加一些特定于模块的上下文信息,我想为 all 记录器的处理程序执行此操作。枚举所有其他记录器可能使用的所有处理程序,暂时更改格式,然后将其更改回来以免破坏其他记录器不是解决方案。
    • 这确实是在这种更极端的情况下必须要做的事情,在这种情况下,人们几乎无法控制处理程序在记录器之间的分布方式。这是一个具体而不幸的案例。现在,仍然可以按照这个答案来做这件事,所以对我来说看起来很好(免责声明:我都问了这个问题并提供了这个答案)。有趣的是,在上面的评论中提出一个特定于案例大纲的问题,以及相应的具体答案;这属于另一个 StackOverflow 条目。
    • 有没有办法改变记录器本身的格式,而不仅仅是处理程序?
    【解决方案2】:

    有几种方法。除了 already documented ones (extra 记录调用的参数,LoggerAdapter, Filter) 之外,另一种方法是指定自定义格式化类,您可以随时了解其实例正在处理的文件。例如:

    class FileProcessingFormatter(logging.Formatter):
        def __init__(self, fmt, datefmt=None, current_file=None):
            super(FileProcessingFormatter, self).__init__(fmt, datefmt)
            self.orig_fmt = fmt
            self.current_file = current_file
    
        def format(self, record):
            if self.current_file is None:
                self._fmt = self.orig_fmt.replace('__FILE_PLACEHOLDER__', '')
            else:
                self._fmt = self.orig_fmt.replace('__FILE_PLACEHOLDER__',
                                ' while processing %r' % self.current_file)
            return super(FileProcessingFormatter, self).format(record)
    

    实例化格式化程序...

    f = FileProcessingFormatter('%(levelname)s__FILE_PLACEHOLDER__ %(message)s')
    for h in relevant_handlers:
        h.setFormatter(f)
    

    处理文件...

    f.current_file = fn
    process_file(fn)
    f.current_file = None
    

    这非常简单 - 例如,如果文件处理由不同的线程同时完成,则不适用于线程环境。

    更新:虽然可以通过logging.getLogger().handlers 访问根记录器的处理程序,但这是一个可能会更改的实现细节。由于您的要求不是那么基本,您也许可以使用 dictConfig() 来配置您的日志记录(可通过 logutils 项目获得旧版本的 Python)。

    【讨论】:

    • 谢谢。可以为仅通过logging.basicConfig() 定义的处理程序获得relevant_handlers 吗?如果不是,如何定义relevant_handlers
    • 感谢您的更新。在实施任何具体的解决方案之前,我需要阅读你的 Logging HOWTO——也许更多——
    • 这在 Python 3.8 中对我不起作用。我检查了selfreturn super(FileProcessingFormatter, self).format(record) 之前有正确的_fmt 属性,但它仍然会使用原始self._fmt 格式化
    【解决方案3】:

    我不推荐这个;但是你可以说假设第一个根处理程序是搞砸的并直接修改它

    import logging
    ROOT_LOGGER = logging.getLogger()
    ROOT_LOGGER.handlers[0].setFormatter(logging.Formatter(
        '%(asctime)s:%(levelname)s:%(name)s:%(message)s\n'
    ))
    

    如果您在任何具有托管日志记录的系统中;这可能会射中你的脚;最好能够确定对要修改的处理程序的确切引用并对其进行修改;

    但没有人关心它是否正常工作?/s

    【讨论】:

    • 这可能是一个快速而肮脏的临时解决方案。关心未来可能出现问题的代码的人确实关心代码是否大部分工作但从根本上被破坏,因为这增加了代码以后由于某些神秘且代价高昂的原因而失败的风险。
    【解决方案4】:

    据我所知,@Vinay Sajip 在更新中提到的 dictConfig() 是最好的方法。

    这是 Python 3.8 中的一个工作示例:

    import logging
    from logging.config import dictConfig    
    FI_PATH_CONSUMERS_LOG = "/project/test_log.log"
    LOG_FORMAT = "%(asctime)s %(levelname)-8s [%(name)s] %(message)s"
    LOG_LEVEL = "INFO"
    LOG_DATEFMT = "%Y-%m-%d %H:%M:%S"
    
    logging.basicConfig(
        filename=FI_PATH_CONSUMERS_LOG,
        format=LOG_FORMAT,
        level=getattr(logging, LOG_LEVEL),
        datefmt=LOG_DATEFMT,
    )
    
    def create_new_format(format_additions: str=""):
        """
        This adjusts both loggers, but you can
        also make changes specific to each.
        """
        alt_new_formats = {
            "event_format": {
                "format": LOG_FORMAT + format_additions,
                "datefmt": LOG_DATEFMT,
            },
        }
    
        changed_setting = {
            "version": 1,
            "formatters": alt_new_formats,
            "handlers": {
                "to_file": {
                    "class": "logging.FileHandler",
                    "filename": FI_PATH_CONSUMERS_LOG,
                    "formatter": "event_format",
                    "level": LOG_LEVEL,
                },
            },
            "loggers": {
                "": {
                    "handlers": ["to_file"],
                },
            },
            "disable_existing_loggers": False,
        }
        return changed_setting
    

    你可以这样运行:

    logger = logging.getLogger()  # root logger
    logger_2 = logging.getLogger("logger 2")  # child logger with name "logger 2"
    msg_test = "Testing message."
    
    logger.info(msg_test)
    logger_2.info(msg_test)
    
    dictConfig(create_new_format(" [Adjust formatting]"))
    logger.info(msg_test)
    logger_2.info(msg_test)
    
    dictConfig(create_new_format())
    logger.info(msg_test)
    logger_2.info(msg_test)
    

    这将为您提供以下输出:

    2021-09-16 14:47:48 INFO     [root] Testing message.
    2021-09-16 14:47:48 INFO     [logger 2] Testing message.
    2021-09-16 14:47:48 INFO     [root] Testing message. [Adjust formatting]
    2021-09-16 14:47:48 INFO     [logger 2] Testing message. [Adjust formatting]
    2021-09-16 14:47:48 INFO     [root] Testing message.
    2021-09-16 14:47:48 INFO     [logger 2] Testing message.
    

    【讨论】:

    • 谢谢。我更新了代码,使其更健壮:更改basicConfig() 中的参数最终阻止了代码正确恢复日志记录格式。
    • 你确定吗?因为编辑停止了示例代码的运行,使其不再是一个工作示例。如果我没记错的话,我还尝试使用dictConfig 进行初始化,而不是将变量传递给basicConfig,但它也不起作用.我不能说为什么会这样。将变量传递给 basicConfig 确实会引入“重复自己”的方面,但使用常量,就像在示例中所做的那样,应该可以缓解这一方面。
    • 恢复了更改以使代码再次工作,还重构了代码以使其更易于阅读。
    【解决方案5】:

    如果你想动态改变日志的格式。 可以做成这样的。

    logger = logging.getLogger()
    # Change format of handler for the logger
    logger.handlers[0].setFormatter(logging.Formatter('%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s'))
    # Print log
    logging.info(log)
    # return other format
    logger.handlers[0].setFormatter(logging.Formatter('%(message)s'))
    

    【讨论】:

    • 这不会返回到以前的日志消息格式:而是将格式更改为其他格式。此外,解释为什么handlers[0] 是合适的会很有趣。
    猜你喜欢
    • 2012-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-20
    • 1970-01-01
    • 2016-04-09
    • 2011-07-27
    相关资源
    最近更新 更多