Python Logger无法写入文件但PM2可见日志,hasHandlers()校验异常
问题背景
每30秒通过Django命令创建ExampleClass实例,为避免重复添加日志Handler,使用hasHandlers()做校验,结果日志文件能自动创建但无内容写入,仅能在PM2控制台看到日志输出。替换为len(self.example_logger.handlers) == 0的校验逻辑后,日志正常写入文件,但不清楚hasHandlers()失效的根本原因。
相关代码
class ExampleClass: def __init__(self): self.example_logger = logging.getLogger(__name__) handler = TimedRotatingFileHandler('logs/example.log', when='D', interval=1, backupCount=0, encoding=None, delay=False, utc=False) if not self.example_logger.hasHandlers(): self.example_logger.addHandler(handler) self.example_logger.setLevel(logging.DEBUG)
根本原因分析
logging.hasHandlers()的核心逻辑是递归检查当前logger及其所有祖先logger是否存在Handler,只要任意一个层级的logger配置了Handler,就会返回True。
在你的场景中:
- Django项目通常会通过
settings.py中的LOGGING配置根logger,根logger默认会带有控制台输出的Handler(这就是PM2能看到日志的原因)。 - 当你调用
self.example_logger.hasHandlers()时,虽然当前__name__对应的logger自身没有配置Handler,但因为它的祖先(根logger)存在Handler,所以hasHandlers()返回True,导致你跳过了添加文件Handler的逻辑,最终日志无法写入文件。
而len(self.example_logger.handlers) == 0仅检查当前logger自身的Handler列表,不会递归查询祖先logger的配置,因此能准确判断当前logger是否需要添加新的Handler。
解决方案与注意事项
方案1:保留len(handlers)校验逻辑
继续使用len(self.example_logger.handlers) == 0作为判断条件,这是生产环境安全的方案,它完全不受祖先logger配置的干扰,确保只有在当前logger没有Handler时才添加文件Handler。
方案2:结合hasHandlers()与propagate配置
如果希望继续使用hasHandlers(),可以通过设置当前logger的propagate=False切断与祖先logger的日志传播关系,这样hasHandlers()只会检查当前logger自身的Handler:
class ExampleClass: def __init__(self): self.example_logger = logging.getLogger(__name__) self.example_logger.propagate = False # 切断日志向祖先logger传播 handler = TimedRotatingFileHandler('logs/example.log', when='D', interval=1, backupCount=0, encoding=None, delay=False, utc=False) if not self.example_logger.hasHandlers(): self.example_logger.addHandler(handler) self.example_logger.setLevel(logging.DEBUG)
注意:设置
propagate=False后,当前logger的日志不会再传递给根logger,PM2控制台将无法看到这部分日志,需要根据业务需求权衡是否使用。
内容的提问来源于stack exchange,提问作者Sokmixtp

