如何避免Python logging库出现重复输出问题?
你猜的没错,问题确实出在每次调用set_logger时都会给根logger(logging.getLogger(''))添加一个新的StreamHandler,导致handler不断累积,每多运行一次就多一份输出。下面是几种成熟的解决思路,你可以根据自己的场景选择:
方法一:检查已有Handler,避免重复添加
在添加新的console handler前,先遍历根logger的现有handler,判断是否已经存在StreamHandler实例(或者更精确地判断是否是我们要添加的那种console handler),如果存在就不再重复添加。修改你的set_logger函数如下:
import logging def set_logger(file_name): '''sets up output via loggers and a output file.''' output = ('%(asctime)-20s %(filename)-15s %(funcName)-15s %(levelname)-8s %(message)s') # 配置文件日志 logging.basicConfig(level=logging.DEBUG, format=output, filename=file_name, filemode='w') # 检查是否已有console handler root_logger = logging.getLogger('') console_handler_exists = False for handler in root_logger.handlers: if isinstance(handler, logging.StreamHandler): console_handler_exists = True break if not console_handler_exists: # 添加console handler console = logging.StreamHandler() console.setLevel(logging.INFO) formatter = logging.Formatter('%(message)s') console.setFormatter(formatter) root_logger.addHandler(console)
这个方法的好处是保留已有的handler配置,不会破坏其他地方可能添加的handler。如果你需要更精确的判断(比如确保是我们自己定义的那种只输出%(message)s的console handler),可以额外检查handler的formatter是否匹配:
# 在判断时增加formatter检查 if isinstance(handler, logging.StreamHandler): if handler.formatter._fmt == '%(message)s' and handler.level == logging.INFO: console_handler_exists = True break
方法二:重置根Logger的Handler(适合需要完全重置的场景)
如果你每次调用set_logger都希望完全重置logger的配置(移除所有旧handler,重新添加),可以先清空根logger的handlers:
def set_logger(file_name): '''sets up output via loggers and a output file.''' output = ('%(asctime)-20s %(filename)-15s %(funcName)-15s %(levelname)-8s %(message)s') root_logger = logging.getLogger('') # 移除所有已存在的handler root_logger.handlers.clear() # 重新配置文件日志 logging.basicConfig(level=logging.DEBUG, format=output, filename=file_name, filemode='w') # 添加console handler console = logging.StreamHandler() console.setLevel(logging.INFO) formatter = logging.Formatter('%(message)s') console.setFormatter(formatter) root_logger.addHandler(console)
注意:这个方法会移除根logger上所有的handler,包括其他模块可能添加的,所以如果你的项目里有其他地方也在配置logger,要谨慎使用。
方法三:使用单例模式确保Logger只被配置一次
如果你的set_logger只需要被调用一次,也可以用单例模式来保证配置只执行一次:
import logging def set_logger(file_name): # 用一个函数属性标记是否已经配置过 if hasattr(set_logger, '_configured') and set_logger._configured: return output = ('%(asctime)-20s %(filename)-15s %(funcName)-15s %(levelname)-8s %(message)s') logging.basicConfig(level=logging.DEBUG, format=output, filename=file_name, filemode='w') console = logging.StreamHandler() console.setLevel(logging.INFO) formatter = logging.Formatter('%(message)s') console.setFormatter(formatter) logging.getLogger('').addHandler(console) # 标记已配置 set_logger._configured = True
这种方法简单直接,适合只需要初始化一次logger的场景,比如在项目启动时调用一次就够了。
额外提示
在交互式环境(比如Jupyter Notebook)或者长期运行的进程中,logger的状态会被保留,所以这类重复添加handler的问题会更常见。上面的方法都能很好地解决这个问题,你可以根据自己的项目规模和需求选择最合适的方案。
内容的提问来源于stack exchange,提问作者Will

