问题起源:

​ 在学习了python的函数式编程后,又接触到了logging这样一个强大的日志模块。为了减少重复代码,应该不少同学和我一样便迫不及待的写了一个自己的日志函数,比如下面这样:

import logging

# 这里为了便于理解,简单的展示了一个输出到屏幕的日志函数
def my_log():
logger = logging.getLogger('mysql.log') ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(fmt)
logger.addHandler(ch) return logger my_log().error('run one')
my_log().error('run two')
my_log().error('run three')

函数写好了,看起来似乎也没有问题,我们来运行一下!

结果如下:

2018-06-21 13:06:37,569 - mysql.log - ERROR - run one
2018-06-21 13:06:37,569 - mysql.log - ERROR - run two
2018-06-21 13:06:37,569 - mysql.log - ERROR - run two
2018-06-21 13:06:37,569 - mysql.log - ERROR - run three
2018-06-21 13:06:37,569 - mysql.log - ERROR - run three
2018-06-21 13:06:37,569 - mysql.log - ERROR - run three

日志居然重复输出了,且数量递增。


问题解析

  • 实际上logger = logging.getLogger('mysql.log')在执行时,没有每次生成一个新的logger,而是先检查内存中是否存在一个叫做‘mysql.log’的logger对象,存在则取出,不存在则新建。

  • 实例化的logger对象具有‘handlers’这样一个属性来存储 Handler,代码演示如下:

import logging

def my_log():
logger = logging.getLogger('mysql.log')
# 每次被调用后打印出logger的handlers列表
print(logger.handlers) ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(fmt) logger.addHandler(ch) return logger my_log().error('run one')
my_log().error('run two')
my_log().error('run three')

运行结果:

[]
2018-06-21 13:26:14,059 - mysql.log - ERROR - run one
[<StreamHandler <stderr> (ERROR)>]
2018-06-21 13:26:14,060 - mysql.log - ERROR - run two
2018-06-21 13:26:14,060 - mysql.log - ERROR - run two
[<StreamHandler <stderr> (ERROR)>, <StreamHandler <stderr> (ERROR)>]
2018-06-21 13:26:14,060 - mysql.log - ERROR - run three
2018-06-21 13:26:14,060 - mysql.log - ERROR - run three
2018-06-21 13:26:14,060 - mysql.log - ERROR - run three

  1. logger.handlers 最初是一个空列表,执行 ‘logger.addHandler(ch)’ 添加一个 ‘StreamHandler’ ,输出一条日志
  2. 在第二次被调用时, logger.handlers 已经存在一个 ‘StreamHandler’ ,再次执行 ‘logger.addHandler(ch)’ 就会再次添加一个 ‘StreamHandler’ ,此时的logger有两个 ‘StreamHandler’ ,输出两条重复的日志
  3. 在第三次被调用时, logger.handlers 已经存在两个 ‘StreamHandler’ ,再次执行 ‘logger.addHandler(ch)’ 就会再次添加一个,此时的logger有三个 ‘StreamHandler’ ,输出三条重复的日志

解决办法

1.改名换姓

 import logging

 # 为日志函数添加一个name,每次调用时传入不同的日志名
def my_log(name):
logger = logging.getLogger(name) ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(fmt)
logger.addHandler(ch) return logger my_log('log1').error('run one')
my_log('log2').error('run two')
my_log('log3').error('run three')

运行结果:

2018-06-21 13:40:51,685 - log1 - ERROR - run one
2018-06-21 13:40:51,685 - log2 - ERROR - run two
2018-06-21 13:40:51,685 - log3 - ERROR - run three

2.及时清理(logger.handlers.clear)

 import logging

 def my_log():
logger = logging.getLogger('mysql.log')
# 每次被调用后,清空已经存在handler
logger.handlers.clear() ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(fmt) logger.addHandler(ch) return logger my_log().error('run one')
my_log().error('run two')
my_log().error('run three')

ps:removeHandler方法(兼容性较差)

 # 这种写法下的可以使用removeHandler方法(logger.handlers.clear也可以使用在这种写法的函数内)
import logging def my_log(msg):
logger = logging.getLogger('mysql.log') ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(fmt) logger.addHandler(ch)
logger.error(msg) # 在使用完ch后从移除Handler
logger.removeHandler(ch) my_log('run one')
my_log('run two')
my_log('run three')

3.用前判断

 import logging

 def my_log():
logger = logging.getLogger('mysql.log')
# 判断logger是否已经添加过handler,是则直接返回logger对象,否则执行handler设定以及addHandler(ch)
if not logger.handlers:
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(fmt) logger.addHandler(ch) return logger my_log().error('run one')
my_log().error('run two')
my_log().error('run three')

总结

​ 第一次遇到日志重复输出问题,那时还没有学习到面向对象编程的内容,当时并没有真正理解logging模块。学习完面向对象编程后,回过头来再思考这些问题有了豁然开朗的感觉。

​ 比如起初对logging.getLogger的实际原理不是很理解,在学习了面向对象编程中的hasattr、getattr、setattr这样一些方法后就恍然大悟了。所以诸君如果现在还是对logging模块不太理解,不妨先不纠结于这些细节,继续学下去。

​ 知识面扩充后,曾经的一些难题自然就会迎刃而解:)


参考内容:

luffycity : https://www.luffycity.com/home

The Python Standard Library

huilan_same :https://blog.csdn.net/huilan_same/article/details/51858817

分析python日志重复输出问题的更多相关文章

  1. 浅析python日志重复输出问题

    浅析python日志重复输出问题 问题起源: ​ 在学习了python的函数式编程后,又接触到了logging这样一个强大的日志模块.为了减少重复代码,应该不少同学和我一样便迫不及待的写了一个自己的日 ...

  2. 【转载】浅析python日志重复输出问题

    出处:https://www.cnblogs.com/huang-yc/p/9209096.html 问题起源: ​ 在学习了python的函数式编程后,又接触到了logging这样一个强大的日志模块 ...

  3. python日志重复输出

    ​ 在学习了python的函数式编程后,又接触到了logging这样一个强大的日志模块.为了减少重复代码,应该不少同学和我一样便迫不及待的写了一个自己的日志函数,比如下面这样: # 这里为了便于理解, ...

  4. python日志等级输出删选

    有时候我们会删选一下输出的信息 当做日志进行文件保存 但是我们程序中有可能有自己不想存到日志文件中的输出信息 我们要做一些的删选  然后进行保存 代码如下: #!/usr/bin/python # - ...

  5. log日志重复输出问题(没弄明白原因)

    在别的模块调用定义好的函数 输出的日志出现第一次输出输出一条,第二次输出输出两条...的情况 最后在定义函数处remove了句柄 引用了https://blog.csdn.net/huilan_sam ...

  6. Log4j 输出的日志中时间比系统时间少了8小时的解决方法,log4j日志文件重复输出

    1. 第一个问题:时间少了8小时 Log4j 输出的日志中,时间比系统时间少了8小时,但是 eclipse 控制台输出的日志的时间却是对的. log4j配置如下: #all logger output ...

  7. 一天,python搞个分析NGINX日志的脚本

    准备给ZABBIX用的. 统计接口访问字次,平均响应时间,4XX,5XX次数 以后可以再改进.. #!/usr/bin/env python # coding: utf-8 ############# ...

  8. Azure HDInsight 上的 Spark 群集配合自定义的Python来分析网站日志

    一.前言:本文是个实践博客,演示如何结合使用自定义库和 HDInsight 上的 Spark 来分析日志数据. 我们使用的自定义库是一个名为 iislogparser.py的 Python 库. 每步 ...

  9. Python 日志输出中添加上下文信息

    Python日志输出中添加上下文信息 除了传递给日志记录函数的参数(如msg)外,有时候我们还想在日志输出中包含一些额外的上下文信息.比如,在一个网络应用中,可能希望在日志中记录客户端的特定信息,如: ...

随机推荐

  1. [BJOI2019] 删数

    https://www.luogu.org/problemnew/show/P5324 题解 首先我们需要弄清这个答案是什么. 对于一个长度为n的序列,那么它先删的肯定是\(n\),删完之后它就会跳到 ...

  2. vue(初探预渲染)

    ---恢复内容开始--- 一.前言 1.简介预渲染                     2.案例演示(不配置预渲染)                     3.配置预渲染, 二.主要内容 1.简 ...

  3. Redis-Cluster操作命令大全

    今天整理下redis-cluster操作命令 一.Cluster操作命令 CLUSTER INFO 打印集群的信息 CLUSTER NODES 列出集群当前已知的所有节点(node),以及这些节点的相 ...

  4. Jmeter接口测试操作

    一.Jmeter接口测试操作步骤 1. 启动jmeter,右键点击测试计划-->添加-->Threads(Users)-->线程组,点击线程组菜单,右边显示参数设置. 2 . 右键点 ...

  5. spring的事件驱动模型

    在工作中会遇到这样的业务,生成一个订单后需要给指定的用户发送短信或者邮件,但是短信或者邮件发送失败又不会影响正常的业务: 这里介绍通过ApplicationContext和spring的@EventL ...

  6. 使用第三方工具Thumbnailator动态改变图片尺寸

    Thumbnailator项目git地址:https://github.com/coobird/thumbnailator 使用步骤 1.添加依赖 <!-- Thumbnailator图片处理 ...

  7. 关于Java的volatile

    volatile的作用 1.防止指令重排序 首先要理解什么是指令重排序?指令重排序的利弊?后续举例说明 2.多线程访问共享资源时,缓解synchronized重量级锁带来的性能问题 但是volatil ...

  8. Python:正则表达式(二):如何使用re.search()返回的匹配对象中的具体内容呢??

    在上一篇中讲述了re.seach()会返回一个对象格式的数据,如下:<_sre.SRE_Match object; span=(16, 24), match='${phone}'> 那么问 ...

  9. 001 爬虫的基本概念以及urllib的request和parse

    1.http的请求方式: get请求 优点:比较便捷 缺点:不安全.长度有限制post请求 优点:比较安全.数据整体没有限制.可以上传文件putdelete(删除一些信息) 发送网络请求(可以带一定的 ...

  10. DC综合简单总结(2)

    DC综合简单总结(2) 建立时间和保持时间和数据输出延时时间 一.概念 建立时间和保持时间都是针对触发器的特性说的. 建立时间(Tsu:set up time) 是指在触发器的时钟信号上升沿到来以前, ...