zoukankan      html  css  js  c++  java
  • 分析python日志重复输出问题

    问题起源:

    ​ 在学习了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.改名换姓

     1 import logging
     2 
     3 
     4 # 为日志函数添加一个name,每次调用时传入不同的日志名
     5 def my_log(name):
     6     logger = logging.getLogger(name)
     7 
     8     ch = logging.StreamHandler()
     9     ch.setLevel(logging.ERROR)
    10     fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    11     ch.setFormatter(fmt)
    12     logger.addHandler(ch)
    13 
    14     return logger
    15 
    16 
    17 my_log('log1').error('run one')
    18 my_log('log2').error('run two')
    19 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)

     1 import logging
     2 
     3 
     4 def my_log():
     5     logger = logging.getLogger('mysql.log')
     6     # 每次被调用后,清空已经存在handler
     7     logger.handlers.clear()
     8 
     9     ch = logging.StreamHandler()
    10     ch.setLevel(logging.ERROR)
    11     fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    12     ch.setFormatter(fmt)
    13 
    14     logger.addHandler(ch)
    15 
    16     return logger
    17 
    18 
    19 my_log().error('run one')
    20 my_log().error('run two')
    21 my_log().error('run three')

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

     1 # 这种写法下的可以使用removeHandler方法(logger.handlers.clear也可以使用在这种写法的函数内)
     2 import logging
     3 
     4 
     5 def my_log(msg):
     6     logger = logging.getLogger('mysql.log')
     7 
     8     ch = logging.StreamHandler()
     9     ch.setLevel(logging.ERROR)
    10     fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    11     ch.setFormatter(fmt)
    12 
    13     logger.addHandler(ch)
    14     logger.error(msg)
    15     
    16     # 在使用完ch后从移除Handler
    17     logger.removeHandler(ch)
    18 
    19 
    20 my_log('run one')
    21 my_log('run two')
    22 my_log('run three')

    3.用前判断

     1 import logging
     2 
     3 
     4 def my_log():
     5     logger = logging.getLogger('mysql.log')
     6     # 判断logger是否已经添加过handler,是则直接返回logger对象,否则执行handler设定以及addHandler(ch)
     7     if not logger.handlers:
     8         ch = logging.StreamHandler()
     9         ch.setLevel(logging.ERROR)
    10         fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    11         ch.setFormatter(fmt)
    12 
    13         logger.addHandler(ch)
    14 
    15     return logger
    16 
    17 
    18 my_log().error('run one')
    19 my_log().error('run two')
    20 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

  • 相关阅读:
    《校园封神榜》第二阶段个人工作总结——第五天
    寻找水王2——寻找三个小水王
    站立会议04(第二次冲刺)
    站立会议03(第二次冲刺)
    站立会议02(第二次冲刺)
    站立会议01(第二次冲刺)
    测试计划
    评价cnblogs.com的用户体验
    第一次冲刺各组评价的回复
    第一次冲刺对各组的评价
  • 原文地址:https://www.cnblogs.com/quegai18/p/10031246.html
Copyright © 2011-2022 走看看