Python3.10中warnings.captureWarnings(True)日志格式异常问题排查
Python 3.10中py.warnings日志消息显示为%s的修复方案
我用warnings.captureWarnings(True)将程序警告捕获到数据库的单独表中,但遇到了py.warnings日志的消息始终显示为%s的问题。定位后发现,Python 3.10的logging库在输出警告时使用了logger.warning("%s", s)的写法,导致LogRecord的msg字段固定为%s,实际警告消息存储在record.args中。这个问题在Python 3.11+中已通过BPO-46557修复,但如果不想升级版本,可以用以下几种方案解决:
方法1:在Handler中获取格式化后的完整消息
直接利用LogRecord.getMessage()方法获取已经格式化好的完整消息,代替直接读取record.msg。这是最简便的方案,只需修改自己的日志处理器代码。
修改SQLAlchemyHandler的emit方法:
def emit(self, record): trace = None exc = record.__dict__['exc_info'] if exc: trace = traceback.format_exc() log = self.log_class( logger=record.__dict__['name'], level=record.__dict__['levelname'], trace=trace, msg=record.getMessage(), # 替换原来的record.__dict__['msg'] source_loc=f"{record.__dict__['pathname']}:{record.__dict__['lineno']}") self.session.add(log) self.session.commit()
方法2:Monkey-Patch warnings模块的日志输出逻辑
直接修改warnings模块中发送日志的逻辑,替换成不使用格式化占位符的调用方式,从源头解决问题。
在初始化logging的代码前添加以下代码:
import warnings import logging import linecache def _showwarning(message, category, filename, lineno, file=None, line=None): if file is not None: if line is None: line = linecache.getline(filename, lineno) warnings._showwarnmsg_impl(message, category, filename, lineno, file, line) else: s = warnings._formatwarnmsg_impl(message, category, filename, lineno, line) logger = logging.getLogger("py.warnings") # 直接传入完整消息,不再使用%s格式化 logger.warning(s) warnings.showwarning = _showwarning # 后续的logging初始化代码 logging.captureWarnings(True) warnings_logger = logging.getLogger("py.warnings") warnings_logger.setLevel(logging.DEBUG) from db_logging import SQLAlchemyWarningHandler handler = SQLAlchemyWarningHandler() warnings_logger.addHandler(handler)
方法3:给py.warnings日志添加过滤器修正LogRecord
创建一个日志过滤器,在日志被处理器处理前,修正py.warnings对应的LogRecord,将msg替换为实际消息内容。
import logging class FixWarningsLogFilter(logging.Filter): def filter(self, record): if record.name == "py.warnings" and record.msg == "%s" and record.args: # 替换msg为实际消息,清空args避免后续格式化问题 record.msg = record.args[0] record.args = () return True # 在添加Handler前注册过滤器 logging.captureWarnings(True) warnings_logger = logging.getLogger("py.warnings") warnings_logger.setLevel(logging.DEBUG) # 添加过滤器 warnings_logger.addFilter(FixWarningsLogFilter()) from db_logging import SQLAlchemyWarningHandler handler = SQLAlchemyWarningHandler() warnings_logger.addHandler(handler)
方案对比
- 方法1:侵入性最低,仅修改自身处理器代码,不影响其他日志逻辑,推荐优先使用。
- 方法2:彻底修复源头问题,所有处理
py.warnings的Handler都会受益,但属于Monkey-Patch,需注意兼容性。 - 方法3:通过过滤器修正日志记录,对所有绑定到
py.warnings的Handler生效,适合多Handler场景。
内容的提问来源于stack exchange,提问作者Stev_k
相关产品推荐
相关产品推荐

