Python logging.config.dictConfig封装类后日志无法输出到控制台与文件
问题现象
封装Python logging的dictConfig配置类后,预期日志同时输出到控制台与指定路径/tmp/xxxx.log,实际运行未生效;直接执行类内部的配置逻辑时功能可以正常运行。
根因分析
核心问题是类初始化时位置参数传递顺序错误:
Get_Logger的__init__方法定义的形参顺序为(logfile, log_handler),即第一个参数接收日志文件路径,第二个参数接收logger名称- 实例化时传入的第一个参数是logger名称
"debug_mode",第二个参数才是日志路径logfile - 最终实际赋值结果为
self.logfile = "debug_mode"、self.log_handler = "/tmp/xxxx.log",后续调用logging.getLogger(self.log_handler)获取到的是名为/tmp/xxxx.log的未配置logger,不会加载预先定义的console和localfile handler,日志输出自然不符合预期。
除此之外代码还有两个潜在隐患:
- 全局定义的
logconf_dict是可变对象,每次调用get_logger直接修改全局字典,多次实例化Get_Logger时会出现配置互相覆盖的问题 - logger配置中
propagate设为True,日志会向上传递到根logger,容易出现重复打印的问题
排查步骤
- 打印实例化后对象的
self.logfile、self.log_handler属性,确认赋值是否和预期一致 - 检查
logging.getLogger()传入的名称,是否存在于logconf_dict的loggers配置项中 - 移除类封装,逐行执行类内逻辑对比变量赋值差异,定位传参错误点
修复方案
- 调整形参/实参的传递顺序,优先使用关键字参数传参,从根源避免位置参数顺序错误
- 对基础配置模板做深拷贝后再修改,不要直接改动全局配置字典,避免多实例配置污染
- 将logger的
propagate属性设为False,避免日志重复打印 - 配置加载前清理对应logger的已有handler,避免多次调用时重复添加handler导致日志重复输出
修复后的完整可运行代码:
#!/usr/bin/env python3 import copy import logging from logging.config import dictConfig debug = True class RequireDebugTrue(logging.Filter): def filter(self, record): return debug # 基础配置模板,仅作为初始模板使用,不直接修改 BASE_LOGCONF = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'debug_fmt': { 'format': '[%(asctime)s][%(threadName)s:%(thread)d][%(name)s:' '%(levelname)s][%(module)s:%(funcName)s(%(lineno)d)]:%(message)s' }, 'prod_fmt': { 'format': '[%(asctime)s][%(name)s:%(levelname)s]' '[%(module)s:%(funcName)s(%(lineno)d)]:%(message)s' } }, 'filters': { 'require_debug_true': { '()': RequireDebugTrue, } }, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'debug_fmt', 'filters': ['require_debug_true'] }, 'localfile': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', 'formatter': 'prod_fmt', 'filename': '', # 占位符,运行时动态替换 'maxBytes': 1024 * 1024 * 5, 'backupCount': 5, 'encoding': 'utf8' }, }, 'loggers': { 'debug_mode': { 'handlers': ['console', 'localfile'], 'level': 'DEBUG', 'propagate': False, # 关闭向上传播,避免重复打印 }, 'prod_mode': { 'handlers': ['localfile'], 'level': 'DEBUG', 'propagate': False, }, } } class Get_Logger(object): def __init__(self, log_handler, logfile): # 调整形参顺序符合使用习惯:第一个传logger名称,第二个传日志路径 self.log_handler = log_handler self.logfile = logfile def get_logger(self): # 深拷贝基础模板,不修改全局配置 log_conf = copy.deepcopy(BASE_LOGCONF) log_conf["handlers"]["localfile"]["filename"] = self.logfile logger = logging.getLogger(self.log_handler) # 清理已有handler避免重复打印 logger.handlers.clear() dictConfig(log_conf) return logger if __name__ == '__main__': logfile = "/tmp/xxxx.log" # 推荐用关键字参数传参,彻底避免顺序错误:Get_Logger(log_handler="debug_mode", logfile=logfile) logger_obj = Get_Logger("debug_mode", logfile) logger = logger_obj.get_logger() logger.debug("This is one test.")
额外优化建议
- 多次调用日志配置逻辑时,增加判断避免重复执行
dictConfig,减少不必要的性能开销 - 日志文件路径建议做路径存在性校验,自动创建父目录避免因目录不存在导致文件写入失败
内容的提问来源于stack exchange,提问作者a_noob
相关产品推荐
相关产品推荐

