如何导出并查看Python logging模块的当前配置?
logging的所有配置都不存在隐藏的私有状态,全部挂载在公开可访问的模块对象和实例属性上,不管是你自己写的配置还是第三方模块自动完成的配置,都可以通过以下几种方式完整导出:
方法1:手动遍历核心对象(兼容所有Python版本)
所有已注册的日志器都存在logging.Logger.manager.loggerDict字典中,根日志器可以通过logging.getLogger()直接获取,每个日志器绑定的处理器、过滤器、格式化器都是公开属性,直接遍历就能拿到全部配置,参考代码:
import logging root_logger = logging.getLogger() # 汇总所有日志器,根日志器单独加入,manager中存储的是根日志器之外的实例 all_loggers = {"": root_logger} all_loggers.update(logging.Logger.manager.loggerDict) for logger_name, logger_inst in all_loggers.items(): # manager里可能存未初始化的占位实例,直接跳过 if not isinstance(logger_inst, logging.Logger): continue show_name = logger_name if logger_name else "root" print(f"\n===== 日志器 [{show_name}] 配置 =====") print(f"日志级别: {logging.getLevelName(logger_inst.level)}") print(f"是否向上传播日志: {logger_inst.propagate}") # 打印日志器自身绑定的过滤器 if logger_inst.filters: print(f"绑定过滤器: {[type(f).__name__ for f in logger_inst.filters]}") # 打印绑定的所有处理器 for h_idx, handler in enumerate(logger_inst.handlers): print(f"\n-- 处理器{h_idx} [{type(handler).__name__}] --") print(f" 处理器级别: {logging.getLevelName(handler.level)}") if handler.formatter: print(f" 日志格式: {handler.formatter._fmt}") print(f" 时间格式: {handler.formatter.datefmt}") # 打印不同处理器的特有配置 if hasattr(handler, "baseFilename"): print(f" 输出文件路径: {handler.baseFilename}") if hasattr(handler, "stream"): stream_name = getattr(handler.stream, "name", str(handler.stream)) print(f" 输出流: {stream_name}") if handler.filters: print(f" 绑定过滤器: {[type(f).__name__ for f in handler.filters]}") # 额外检查全局配置 print("\n===== 全局日志配置 =====") print(f"兜底处理器(lastResort): {type(logging.lastResort).__name__},级别:{logging.getLevelName(logging.lastResort.level)}") print(f"日志异常是否抛出: {logging.raiseExceptions}") print(f"当前LogRecord工厂: {logging.getLogRecordFactory().__name__}")
跑这段代码就能把所有日志器、处理器、过滤器、全局配置全打印出来,不会漏任何第三方模块添加的配置。
方法2:用官方方法导出可复现的配置字典(Python 3.12+)
Python 3.12 版本在logging.config模块新增了get_config_dict()方法,会实时遍历当前所有日志组件,生成符合dictConfig格式的配置字典,你可以直接把这个字典序列化存成JSON/YAML,之后调用logging.config.dictConfig()就能1:1复现当前日志配置,用法非常简单:
import json import logging from logging import config current_log_config = config.get_config_dict() # 直接打印或者导出到文件 print(json.dumps(current_log_config, indent=2, ensure_ascii=False))
追踪配置修改的来源
如果想知道到底是哪行代码修改了日志配置,可以在导入可疑的第三方模块之前,给日志配置相关的核心方法打猴子补丁,打印调用栈,比如:
import logging import traceback # 拦截basicConfig调用 _origin_basic = logging.basicConfig def _patched_basic(*args, **kwargs): print("\n[!] 检测到logging.basicConfig被调用,调用栈:") traceback.print_stack() return _origin_basic(*args, **kwargs) logging.basicConfig = _patched_basic # 拦截给日志器添加处理器的操作 _origin_add_hdlr = logging.Logger.addHandler def _patched_add_hdlr(self, hdlr): print(f"\n[!] 检测到日志器[{self.name}]被添加处理器{type(hdlr).__name__},调用栈:") traceback.print_stack() return _origin_add_hdlr(self, hdlr) logging.Logger.addHandler = _patched_add_hdlr # 同理可以给setLevel、config.dictConfig、config.fileConfig等其他修改配置的方法打补丁
注意:不要尝试从
logging.config模块的缓存里读配置——如果第三方模块没有走dictConfig/fileConfig接口,而是直接手动操作日志器对象改配置,这些配置不会存在config模块的缓存里,要么手动遍历属性,要么用3.12+的官方get_config_dict()方法,后者是实时遍历生成的,不存在缓存不一致的问题。
内容的提问来源于stack exchange,提问作者rgov

