Python logging模块重复记录日志问题的原因与解决方案
本文深入解析Python logging模块中常见的重复记录日志问题。通过示例代码展示因未移除Handler导致的日志重复现象,并提供四种有效解决方案,包括移除Handler、判断已有Handler及优雅的处理方式,帮助开发者快速定位并修复日志异常。
日志相关概念
日志是追踪软件运行时事件的重要手段。开发人员可通过调用日志记录方法来标记特定事件的发生,这些事件通常包含可选的变量数据,并具备表示重要性的严重性级别(level)。
日志的作用
通过分析日志,用户可以掌握系统、软件或应用的运行状态;若日志内容足够丰富,还能进一步分析用户的历史操作行为、偏好类型、地域分布等更多维度信息。若应用日志划分了多个级别,则可轻松评估应用健康状况,及时发现并快速定位、解决问题,从而减少损失。
简而言之,记录和分析日志有助于判断系统或软件程序运行是否正常,并在应用出现故障时协助快速定位问题。例如,运维人员在接到报警或反馈后,通常首先查看各类日志以排查问题,大多数情况可在日志中找到答案。开发人员则可通过IDE控制台输出的日志进行调试。经验丰富的运维人员或开发者能迅速通过日志定位根源,可见日志的重要性不容忽视。其作用可总结为以下三点:
- 程序调试
- 监控软件程序运行状态是否正常
- 软件程序运行故障分析与问题定位
若应用日志信息足够详细丰富,还可用于用户行为分析,如分析用户操作行为、类型喜好、地域分布及其他更多信息,进而优化业务、提升商业价值。
发现问题
近期在使用Python的logging模块记录日志时,遇到了重复记录的问题:第一条记录写一次,第二条写两次,第三条写三次……这显然不符合预期。经搜索网络,找到了原因及解决方案:
原因:未移除Handler
解决方案:在日志记录完成后调用removeHandler
修改前示例代码:
import logging
def log(message):
logger = logging.getLogger('testlog')
streamhandler = logging.StreamHandler()
streamhandler.setLevel(logging.ERROR)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s')
streamhandler.setFormatter(formatter)
logger.addHandler(streamhandler)
logger.error(message)
if __name__ == '__main__':
log('hi')
log('hi too')
log('hi three')
修改前输出结果:
2016-07-08 09:17:29,740 - ERROR - testlog - hi 2016-07-08 09:17:29,740 - ERROR - testlog - hi too 2016-07-08 09:17:29,740 - ERROR - testlog - hi too 2016-07-08 09:17:29,740 - ERROR - testlog - hi three 2016-07-08 09:17:29,740 - ERROR - testlog - hi three 2016-07-08 09:17:29,740 - ERROR - testlog - hi three
修改后示例代码:
import logging
def log(message):
logger = logging.getLogger('testlog')
streamhandler = logging.StreamHandler()
streamhandler.setLevel(logging.ERROR)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s')
streamhandler.setFormatter(formatter)
logger.addHandler(streamhandler)
logger.error(message)
# 添加下面一句,在记录日志之后移除句柄
logger.removeHandler(streamhandler)
if __name__ == '__main__':
log('hi')
log('hi too')
log('hi three')
修改后输出结果:
2016-07-08 09:32:28,206 - ERROR - testlog - hi 2016-07-08 09:32:28,206 - ERROR - testlog - hi too 2016-07-08 09:32:28,206 - ERROR - testlog - hi three
深度解析:
经查阅资料可知,问题在于第二次调用log时,通过getLogger(name)获取的是同一个logger实例。该logger中已存在第一次添加的handler,第二次调用又添加了一个新的handler,导致该logger中包含两个相同的handler。以此类推,调用次数越多,handler数量越多。
因此,这里有以下几种解决办法:
- 每次创建不同name的logger,每次都是新logger,从而避免添加多个handler的问题。(注:此方法较为笨拙,但我之前曾这样使用。)
- 如上文所示,每次记录日志后,调用removeHandler()移除该logger中的handler。
- 在log方法中进行判断,若该logger已有handler,则不再添加新handler。
- 与方法2类似,但使用pop方法将logger的handler列表中的handler移除。
以下是方法3与方法4的代码示例:
方法3:
import logging
def log(message):
logger = logging.getLogger('testlog')
# 这里进行判断,如果logger.handlers列表为空,则添加,否则,直接去写日志
if not logger.handlers:
streamhandler = logging.StreamHandler()
streamhandler.setLevel(logging.ERROR)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s')
streamhandler.setFormatter(formatter)
logger.addHandler(streamhandler)
logger.error(message)
if __name__ == '__main__':
log('hi')
log('hi too')
log('hi three')
方法4:
import logging
def log(message):
logger = logging.getLogger('testlog')
streamhandler = logging.StreamHandler()
streamhandler.setLevel(logging.ERROR)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s')
streamhandler.setFormatter(formatter)
logger.addHandler(streamhandler)
logger.error(message)
# 用pop方法把logger.handlers列表中的handler移除,注意如果你add了多个handler,这里需多次pop,或者可以直接为handlers列表赋空值
logger.handlers.pop()
# logger.handler = []
if __name__ == '__main__':
log('hi')
log('hi too')
log('hi three')
上述几种方法经测试均有效,个人觉得方法3的判断逻辑更为优雅,您怎么看?
总结
以上就是本文的全部内容,希望本文的内容对大家的学习或工作具有一定的参考借鉴价值。如有疑问欢迎留言交流,感谢大家对本站的支持。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













