zoukankan      html  css  js  c++  java
  • day5-logging模块

    一、概述

    好的程序开发,往往会兼顾到日志输出的需求,以便给用户提供必要的日志信息便于确认程序运行状态、排错等等。这些日志一般包括程序的正常运行日志、访问日志、错误日志、数据保存日志等类型。在python中logging模块提供了标准的日志接口,可以满足我们对日志输出的各种需求,下面一一详述。

    二、logging模块入门

    2.1 日志级别

    业内常用的日志有五个级别,分别是:debug,info,warning,error和critical,其中debug级别最低,critical级别最高,级别越低,打印的日志等级越多。各个级别的定义如下:

    image

    默认的级别是warning,只有在warning级别和以上级别的日志才会输出,这样可以避免日志过多的问题。当然这个默认的级别是可以自定义修改的,下文详述。

    2.2 标准日志输出

    最简单的日志输出做输出到stdout,看代码:

      1 import logging
      2 
      3 logging.debug('[debug]')
      4 logging.info('[info]')
      5 logging.warning('[warning]')
      6 logging.error('[error]')
      7 logging.critical('[critical]')
      8 
      9 输出:
     10 WARNING:root:[warning]
     11 ERROR:root:[error]
     12 CRITICAL:root:[critical]
     13 
     14 #默认的日志级别是warning,该级别以下的日志没有输出

    2.3 日志输出到文件

    日志输出到文件具有持久化保存的功能,便于随时回溯查看。通过basicConfig可以定义日志输出的参数。关键参数如下:

    image

    当然了,这个日志级别是可以灵活自定义的。

      1 import logging
      2 
      3 logging.basicConfig(filename='running.log', level=logging.INFO)
      4 logging.debug('[debug]')
      5 logging.info('[info]')
      6 logging.warning('[warning]')
      7 logging.error('[error]')
      8 logging.critical('[critical]')

    日志文件输出结果:

    image

    2.4 format格式化输出日志

    前面的示例中,日志的输出没有什么可读性而言,这里可以通过format来增加时间等字段,提高可读性。常用的format相关的参数如下:

    image

    示例程序:

      1 import logging
      2 
      3 logging.basicConfig(filename='running.log', level=logging.INFO, format='%(asctime)s [%(levelname)s] %(module)s %(message)s')
      4 logging.debug('[debug]')
      5 logging.info('service started')
      6 logging.warning('memory leak')
      7 logging.error('can not started')
      8 logging.critical('fatal error')
      9 
     10 程序输出:
     11 2018-02-21 07:30:22,970 [INFO] log service started
     12 2018-02-21 07:30:22,971 [WARNING] log memory leak
     13 2018-02-21 07:30:22,971 [ERROR] log can not started
     14 2018-02-21 07:30:22,971 [CRITICAL] log fatal error

    因为示例程序的文件名为log.py,所以这里日志输出的模块名显示为log。还可以通datefmt参数自定义日期时间输出格式:

      1 import logging
      2 
      3 logging.basicConfig(filename='running.log', level=logging.INFO, format='%(asctime)s [%(levelname)s] %(module)s %(message)s',datefmt='%m/%d/%Y %H:%M:%S')
      4 logging.debug('[debug]')
      5 logging.info('service started')
      6 logging.warning('memory leak')
      7 logging.error('can not started')
      8 logging.critical('fatal error')
      9 
     10 输出:
     11 02/21/2018 07:41:02 [INFO] log service started
     12 02/21/2018 07:41:02 [WARNING] log memory leak
     13 02/21/2018 07:41:02 [ERROR] log can not started
     14 02/21/2018 07:41:02 [CRITICAL] log fatal error

    三、logging模块进阶

    前文讲述了日志输出的一些基本用法,复杂的用法还需要进阶学习:

    3.1 logging模块的主要类概述

    python使用logging模块记录日志涉及四个主要类:

    ①logger:记录器,提供了应用程序可以直接使用的接口。

    ②handler:处理器,将(logger创建的)日志记录发送到合适的目的输出。必须有handler才能捕获输出的日志。

    ③filter:过滤器,对日志进行过滤来决定输出哪条日志记录。

    ④formatter:格式化器,决定日志记录的最终输出格式。

    3.2 logger详解

    Logger是一个树形层级结构,在使用接口debug,info,warn,error,critical之前必须创建Logger实例,即创建一个记录器,如果没有显式的进行创建,则默认创建一个root logger,并应用默认的日志级别(WARN),处理器Handler(StreamHandler,即将日志信息打印输出在标准输出上),和格式化器Formatter(默认的格式即为第一个简单使用程序中输出的格式)。

    创建方法: logger = logging.getLogger(logger_name)

    创建Logger实例后,可以使用以下方法进行相关设置:

    • logger.setLevel(logging.ERROR) # 设置日志级别为ERROR,即只有日志级别大于等于ERROR的日志才会输出
    • logger.addHandler(handler_name) # 为Logger实例增加一个处理器
    • logger.removeHandler(handler_name) # 为Logger实例删除一个处理器
    • logger.addFilter(filt) #为logger实例增加过滤器
    • logger.removeFilter(filt) #为logger实例删除过滤器
    • logger.debug()、logger.info()、logger.warning()、logger.error()、logger.critical() #设定指定级别的日志输出
    • len(logger.handlers) #获取handler的个数

    3.3 handler详解

    handler对象负责发送相关的信息到指定目的地。Python的日志系统有多种Handler可以使用。有些Handler可以把信息输出到控制台,有些Logger可以把信息输出到文件,还有些 Handler可以把信息发送到网络上。如果觉得不够用,还可以编写自己的Handler。可以通过addHandler()方法添加多个多handler 。

    Handler处理器类型有很多种,比较常用的有三个,StreamHandler,FileHandler,NullHandler,详情可以访问Python logging.handlers
    创建StreamHandler之后,可以通过使用以下方法设置日志级别,设置格式化器Formatter,增加或删除过滤器Filter。

    • ch.setLevel(logging.WARN) # 指定日志级别,低于WARN级别的日志将被忽略
    • ch.setFormatter(formatter_name) # 设置一个格式化器formatter
    • ch.addFilter(filter_name) # 增加一个过滤器,可以增加多个
    • ch.removeFilter(filter_name) # 删除一个过滤器
    StreamHandler

    创建方法: sh = logging.StreamHandler(stream=None)

    FileHandler

    创建方法: fh = logging.FileHandler(filename, mode='a', encoding=None, delay=False)

    NullHandler

    NullHandler类位于核心logging包,不做任何的格式化或者输出。
    本质上它是个“什么都不做”的handler,由库开发者使用。
    下面讲解handler的重要知识点:

    • logging.StreamHandler

    说明:创建一个handler对象,使用这个Handler可以向类似与sys.stdout或者sys.stderr的任何文件对象(file object)输出信息,也就是屏幕输出。
    它的构造函数是:StreamHandler([strm]),其中strm参数是一个文件对象,默认是sys.stderr。

      1 import logging
      2 
      3 logger = logging.getLogger('testlog') #创建一个logger对象
      4 logger.setLevel(logging.DEBUG) #设定logger的日志级别
      5 
      6 stdout = logging.StreamHandler() #创建一个handler对象,日志输出到stdout
      7 stdout.setLevel(logging.INFO) #设定handler的日志级别
      8 
      9 logger.addHandler(stdout)#为logger增加指定的handler
     10 logger.debug('debug log') #输出日志
     11 logger.info('info log') #输出日志
     12 
     13 输出:
     14 info log

    上述示例程序中,由于handler中设置的日志级别是info,比logger中的debug还要高,因此结果输出中仅仅输出了info级别的日志。

    • logging.FileHandler

    说明:和StreamHandler类似,用于向一个文件输出日志信息,不过FileHandler会帮你打开这个文件。

    它的构造函数是:FileHandler(filename[,mode])。filename是文件名,必须指定一个文件名。mode是文件的打开方式。参见Python内置函数open()的用法。默认是’a',即添加到文件末尾。

      1 import logging
      2 
      3 logger = logging.getLogger('testlog')
      4 logger.setLevel(logging.DEBUG)
      5 
      6 fh = logging.FileHandler('test.log')  #创建一个fileHandler对象,默认追加写
      7 fh.setLevel(logging.INFO) #设置handler的日志级别
      8 
      9 formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s --%(module)s')
     10 fh.setFormatter(formatter) #为handler设定格式化器
     11 
     12 logger.addHandler(fh)
     13 logger.debug('debug log')
     14 logger.info('info log')
     15 logger.error('error log')
     16 
     17 输出:
     18 2018-02-21 10:55:37,804 [INFO] info log --log
     19 2018-02-21 10:55:37,804 [ERROR] error log --log
    • logging.handlers.RotatingFileHandler

    说明:这个Handler类似于上面的FileHandler,但是它可以管理文件大小。当文件达到一定大小(字节数)之后,它会自动进行日志文件滚动切割:将当前日志文件改名,然后创建 一个新的同名日志文件继续输出。比如日志文件是chat.log。当chat.log达到指定的大小之后,RotatingFileHandler自动把 文件改名为chat.log.1。不过,如果chat.log.1已经存在,会先把chat.log.1重命名为chat.log.2。。。最后重新创建 chat.log,继续输出日志信息。

    它的构造函数是:RotatingFileHandler( filename, mode, maxBytes, backupCount),其中filename和mode两个参数和FileHandler一样。
    maxBytes用于指定日志文件的最大文件大小。如果maxBytes为0,意味着日志文件可以无限大,这时上面描述的重命名过程就不会发生。
    backupCount用于指定保留的备份文件的个数。比如,如果指定为2,当上面描述的重命名过程发生时,原有的chat.log.2并不会被更名,而是被删除。

      1 import logging
      2 from logging import handlers
      3 
      4 logger = logging.getLogger(__name__)
      5 logger.setLevel(logging.DEBUG)
      6 
      7 #单个日志文件最大50字节,最多保留3个文件
      8 fh = logging.handlers.RotatingFileHandler(filename='time.log', maxBytes=50, backupCount=3)
      9 fh.setLevel(logging.INFO)
     10 
     11 #设定formatter
     12 formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s --%(module)s')
     13 fh.setFormatter(formatter)
     14 
     15 logger.addHandler(fh)
     16 logger.debug('debug log')
     17 logger.info('info log')
     18 logger.error('error log')
     19 
     20 输出:
     21 time.log.1 文件	2018-02-21 11:08:26,253 [INFO] info log --log
     22 time.log 文件	2018-02-21 11:08:26,254 [ERROR] error log --log

    上述示例程序中由于一行日志的输出刚好达到50字节的日志文件切割临界值,所以两行log输出到了两个日志文件中,且越早输出的日志会写到了后缀越大的日志文件中(最新的日志文件,可认为后缀为0)

    最新补充:
    自己在写一次作业时发现,.RotatingFileHandler存在一下问题:

    (1) 默认写日志的编码不是utf-8,直接通过参数encoding='utf-8'解决

    • logging.handlers.TimedRotatingFileHandler

    说明:这个Handler和RotatingFileHandler类似,不过,它没有通过判断文件大小来决定何时重新创建日志文件,而是间隔一定时间就自动创建新的日志文件。重命名的过程与RotatingFileHandler类似,不过新的文件不是附加数字,而是当前时间。

    它的构造函数是:TimedRotatingFileHandler( filename,when,interval,backupCount),其中filename参数和backupCount参数和RotatingFileHandler具有相同的意义。

    interval是时间间隔。
    when参数是一个字符串。表示时间间隔的单位,不区分大小写。它有以下取值:①S:秒②M:分③H:小时④D:天⑤W :每星期(interval==0时代表星期一)⑥midnight:每天凌晨

      1 import logging
      2 from logging import handlers
      3 import time
      4 
      5 logger = logging.getLogger(__name__)
      6 logger.setLevel(logging.DEBUG)
      7 
      8 #每隔5秒切割一次日志文件,最多保留3份
      9 fh = logging.handlers.TimedRotatingFileHandler(filename='time.log', when='S', interval=3, backupCount=3)
     10 fh.setLevel(logging.INFO)
     11 
     12 formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s --%(module)s')
     13 fh.setFormatter(formatter)
     14 
     15 logger.addHandler(fh)
     16 logger.debug('debug log')
     17 time.sleep(3)
     18 logger.info('info log')
     19 time.sleep(3)
     20 logger.error('error log')
     21 
     22 输出:
     23 time.log				2018-02-21 11:18:05,867 [ERROR] error log --log
     24 time.log.2018-02-21_11-18-02	2018-02-21 11:18:02,842 [INFO] info log --log
     25 time.log.2018-02-21_11-17-59        文件为空,但因为有sleep导致达到日志滚动的时间阈值,所以滚动生成了一个空日志文件

    3.4 Formatter 格式化器

    使用Formatter对象设置日志信息最后的规则、结构和内容,默认的时间格式为%Y-%m-%d %H:%M:%S。

    创建方法: formatter = logging.Formatter(fmt=None, datefmt=None)

    其中,fmt是消息的格式化字符串,datefmt是日期字符串。如果不指明fmt,将使用'%(message)s'。如果不指明datefmt,将使用ISO8601日期格式。

    3.5 filter 过滤器

    Handlers和Loggers可以使用Filters来完成比级别更复杂的过滤。Filter基类只允许特定Logger层次以下的事件。例如用‘A.B’初始化的Filter允许Logger ‘A.B’, ‘A.B.C’, ‘A.B.C.D’, ‘A.B.D’等记录的事件,logger‘A.BB’, ‘B.A.B’ 等就不行。 如果用空字符串来初始化,所有的事件都接受。

    创建方法: filter = logging.Filter(name='')

    filter的示例程序待后续补充更新。

    四、综合运用--把日志同时输出到标准输出和日志文件

    思路比较简单了,先后创建StreamHandler和FileHandler,定义各自的level、formatter,最后add到logger即可。

      1 import logging
      2 
      3 logger = logging.getLogger(__name__)
      4 logger.setLevel(logging.INFO)
      5 
      6 formater = logging.Formatter('%(asctime)s  [%(levelname)s] %(name)s %(message)s ---%(module)s')
      7 
      8 sh = logging.StreamHandler()
      9 sh.setLevel(logging.WARN)
     10 sh.setFormatter(formater)
     11 
     12 fh = logging.FileHandler('test.log')
     13 fh.setLevel(logging.WARN)
     14 fh.setFormatter(formater)
     15 
     16 logger.addHandler(sh)
     17 logger.addHandler(fh)
     18 
     19 logger.debug('debug log')
     20 logger.info('info log')
     21 logger.error('error log')
     22 
     23 输出:
     24 2018-02-21 11:39:38,398  [ERROR] __main__ error log ---log
     25 

    五、概念总结

    以下是相关概念总结:

    熟悉了这些概念之后,有另外一个比较重要的事情必须清楚,即Logger是一个树形层级结构;
    Logger可以包含一个或多个Handler和Filter,即Logger与Handler或Fitler是一对多的关系;
    一个Logger实例可以新增多个Handler,一个Handler可以新增多个格式化器或多个过滤器,而且日志级别将会继承,但Handler中的日志级别会覆盖Logger中的日志级别设置

    六.最新实践踩坑记录--调用handler导致日志重复打印问题

    常言实践出真理,今天在一次作业小项目中实际用到了logging模块来记录交易日志,结果只要不是第一次记录日志,日志都要被妥妥滴重复记录,而且重复的次数很有规律,第二次调用就打印两行重复的日志,第3次调用就是3行重复的,我能想象得到第n次调用就会重复n次了。刚开始以为是自己定义的日志记录函数有问题,后面反复调试确认没有逻辑问题,随后在网上一搜,遇到这个问题的还不少,呵呵哒。下面记录一个给出最简单且容易理解的解释和解决方案(除此之外我看到的大部分都是修改源码,my god,暂时还不是我的菜,而且牵一发而动全身,能避免就避免把)。

    以下论述引用自https://www.cnblogs.com/xqtesting/p/8734001.html

    问题原因:

    当第二次调用log的时候,根据getLogger(__name__)里的name获取同一个logger,而这个logger里已经有了第一次你添加的handler,第二次调用又添加了一个handler,也就是说这个logger里有了两个同样的handler,所以就会出现调用几次就会有几个handler

    解决方案:
    明确了问题原因,搞定他当然就水到渠成了:
    1. 添加handler时不盲目添加,先判断一下,如果已经有了就直接写日志

    if not logger.handlers:
        logger.addHandler(fh)

    2. 每次写完日志就把handler remove掉

    logger.removeHandler(streamhandler)

    哈哈,算是知道了removeHandler大法的一大实际应用场景了。

    还是总结一下吧,经过这次踩坑经历,发现还是要多实践综合性强的实际项目,demo程序一般不超过25行代码,即使有坑也不一定能踩到,而且都是些相对比较片面局限,仅仅针对某个或某几个知识点而来,一个综合性强的项目要胜过n打demo程序了!

    还是多实践多啃骨头多开脑洞把,少年!

  • 相关阅读:
    [学习笔记]多维偏序
    SCOI2009 游戏
    置换群和Burnside引理,Polya定理
    AC自动机——多个kmp匹配
    51nod 1667 概率好题
    分块——优化的暴力
    [Cqoi2014]数三角形——组合数
    C++ 中的导致编译错误汇总
    哈夫曼树Huffman
    导出查询结果到csv文件
  • 原文地址:https://www.cnblogs.com/linupython/p/8456431.html
Copyright © 2011-2022 走看看