一、概述

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

二、logging模块入门

2.1 日志级别

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

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

2.2 标准日志输出

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

 import logging

 logging.debug('[debug]')
logging.info('[info]')
logging.warning('[warning]')
logging.error('[error]')
logging.critical('[critical]') 输出:
WARNING:root:[warning]
ERROR:root:[error]
CRITICAL:root:[critical] #默认的日志级别是warning,该级别以下的日志没有输出

2.3 日志输出到文件

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

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

 import logging

 logging.basicConfig(filename='running.log', level=logging.INFO)
logging.debug('[debug]')
logging.info('[info]')
logging.warning('[warning]')
logging.error('[error]')
logging.critical('[critical]')

日志文件输出结果:

2.4 format格式化输出日志

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

示例程序:

 import logging

 logging.basicConfig(filename='running.log', level=logging.INFO, format='%(asctime)s [%(levelname)s] %(module)s %(message)s')
logging.debug('[debug]')
logging.info('service started')
logging.warning('memory leak')
logging.error('can not started')
logging.critical('fatal error') 程序输出:
2018-02-21 07:30:22,970 [INFO] log service started
2018-02-21 07:30:22,971 [WARNING] log memory leak
2018-02-21 07:30:22,971 [ERROR] log can not started
2018-02-21 07:30:22,971 [CRITICAL] log fatal error

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

 import logging

 logging.basicConfig(filename='running.log', level=logging.INFO, format='%(asctime)s [%(levelname)s] %(module)s %(message)s',datefmt='%m/%d/%Y %H:%M:%S')
logging.debug('[debug]')
logging.info('service started')
logging.warning('memory leak')
logging.error('can not started')
logging.critical('fatal error') 输出:
02/21/2018 07:41:02 [INFO] log service started
02/21/2018 07:41:02 [WARNING] log memory leak
02/21/2018 07:41:02 [ERROR] log can not started
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。

 import logging

 logger = logging.getLogger('testlog') #创建一个logger对象
logger.setLevel(logging.DEBUG) #设定logger的日志级别 stdout = logging.StreamHandler() #创建一个handler对象,日志输出到stdout
stdout.setLevel(logging.INFO) #设定handler的日志级别 logger.addHandler(stdout)#为logger增加指定的handler
logger.debug('debug log') #输出日志
logger.info('info log') #输出日志 输出:
info log

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

  • logging.FileHandler

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

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

 import logging

 logger = logging.getLogger('testlog')
logger.setLevel(logging.DEBUG) fh = logging.FileHandler('test.log') #创建一个fileHandler对象,默认追加写
fh.setLevel(logging.INFO) #设置handler的日志级别 formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s --%(module)s')
fh.setFormatter(formatter) #为handler设定格式化器 logger.addHandler(fh)
logger.debug('debug log')
logger.info('info log')
logger.error('error log') 输出:
2018-02-21 10:55:37,804 [INFO] info log --log
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并不会被更名,而是被删除。

 import logging
from logging import handlers logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) #单个日志文件最大50字节,最多保留3个文件
fh = logging.handlers.RotatingFileHandler(filename='time.log', maxBytes=50, backupCount=3)
fh.setLevel(logging.INFO) #设定formatter
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s --%(module)s')
fh.setFormatter(formatter) logger.addHandler(fh)
logger.debug('debug log')
logger.info('info log')
logger.error('error log') 输出:
time.log.1 文件 2018-02-21 11:08:26,253 [INFO] info log --log
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:每天凌晨

 import logging
from logging import handlers
import time logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) #每隔5秒切割一次日志文件,最多保留3份
fh = logging.handlers.TimedRotatingFileHandler(filename='time.log', when='S', interval=3, backupCount=3)
fh.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s --%(module)s')
fh.setFormatter(formatter) logger.addHandler(fh)
logger.debug('debug log')
time.sleep(3)
logger.info('info log')
time.sleep(3)
logger.error('error log') 输出:
time.log 2018-02-21 11:18:05,867 [ERROR] error log --log
time.log.2018-02-21_11-18-02 2018-02-21 11:18:02,842 [INFO] info log --log
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即可。

 import logging

 logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO) formater = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s %(message)s ---%(module)s') sh = logging.StreamHandler()
sh.setLevel(logging.WARN)
sh.setFormatter(formater) fh = logging.FileHandler('test.log')
fh.setLevel(logging.WARN)
fh.setFormatter(formater) logger.addHandler(sh)
logger.addHandler(fh) logger.debug('debug log')
logger.info('info log')
logger.error('error log') 输出:
2018-02-21 11:39:38,398 [ERROR] __main__ error log ---log

五、概念总结

以下是相关概念总结:

熟悉了这些概念之后,有另外一个比较重要的事情必须清楚,即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程序了!

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

day5-logging模块的更多相关文章

  1. python_way ,day5 模块,模块3 ,双层装饰器,字符串格式化,生成器,递归,模块倒入,第三方模块倒入,序列化反序列化,日志处理

    python_way.day5 1.模块3 time,datetime, json,pickle 2.双层装饰器 3.字符串格式化 4.生成器 5.递归 6.模块倒入 7.第三方模块倒入 8.序列化反 ...

  2. Day5 双层装饰器、字符串格式化、生成器、迭代器、递归

    双层装饰器实现用户登录和权限认证 #!/usr/bin/env python# -*- coding: utf-8 -*-# Author: WangHuafeng USER_INFO = {} de ...

  3. Python_Day_5装饰器、字符串格式化、序列化、内置模块、生成器、迭代器之篇

    一.装饰器 为什么要用装饰器??? 在实际的开发环境中应遵循开发封闭原则,虽然在这个原则是用的面向对象开发,但也适用于函数式编程,简单地说,它规定已经实现的功能代码不是允许修改的,但是可以被扩展: 封 ...

  4. python14 1.带参装饰器 | wrapper 了了解 # 2.迭代器 ***** # 可迭代对象 # 迭代器对象 # for迭代器 # 枚举对象

    ## 复习 '''函数的嵌套定义:在函数内部定义另一个函数 闭包:被嵌套的函数 -- 1.外层通过形参给内层函数传参 -- 2.验证执行 开放封闭原则: 功能可以拓展,但源代码与调用方式都不可以改变 ...

  5. day5--<装饰器、模块、字符串格式化、生成器、迭代器>logging模块

    本节大纲: 一:双层装饰器:一个函数可以被多层装饰器进行装饰,函数渲染(编译)从下到上,函数执行从上到下.如下程序: #!/usr/bin/env python #-*-coding:utf-8-*- ...

  6. 学习PYTHON之路, DAY 5 - PYTHON 基础 5 (装饰器,字符格式化,递归,迭代器,生成器)

    ---恢复内容开始--- 一 装饰器 1 单层装饰器 def outer(func): def inner(): print('long') func() print('after') return ...

  7. python学习之day5,装饰器,生成器,迭代器,json,pickle

    1.装饰器 import os import time def auth(type): def timeer(func): def inner(*args,**kwargs): start = tim ...

  8. python函数与模块(装饰器,文件处理,迭代器等)

    os模块 os.system('命令') 利用python调用系统命令,命令可以是以列表或者元组内的元素形式* res import os res=os.system('ipconfig') prin ...

  9. Python学习日记(十)—— 杂货铺(全局变量补充、Python参数传递、字符串格式化、迭代器、生成器)

    全局变量补充 python自己添加了些全局变量 print(vars()) """结果: {'__name__': '__main__', '__doc__': None ...

  10. 生成器&迭代器,模块

    列表生成式 将列表data=[1,2,3]里的元素都乘2 方法一 data=[1,2,3] for index,i in enumerate(data): data[index] *=2 print( ...

随机推荐

  1. Castle连接多数据库配置

    ActiveRecord 的多数据库配置基本沿袭了 NHibernate 的思想,只不过在配置文件结构上作了些调整.1. 采用继承方式,归纳使用同一数据库的类型.比如 A.B.C.D.E 中 A.B连 ...

  2. Laravel 文档中的 Service Providers

    $this->app->singleton('ReportServices', function () { return new \App\Services\ReportServices( ...

  3. OpenCV KNN 之 使用方法

    http://blog.csdn.net/WL2002200/article/details/43149229 OpenCV 中KNN构造函数如下. C++: CvKNearest::CvKNeare ...

  4. [one day one question] Iscroll 5.0 在chrome上无法滑动

    问题描述: Iscroll 5.0 在chrome上无法滑动,不仅仅在chromePC的开发的时候,在手机上的chrome也有同样的问题,这怎么破? 解决方案: // 关闭 PointerEvent ...

  5. CSS 媒体类型

    CSS 媒体类型 媒体类型允许你指定文件将如何在不同媒体呈现.该文件可以以不同的方式显示在屏幕上,在纸张上,或听觉浏览器等等. 一.媒体类型 一些CSS属性只设计了某些媒体.例如"voice ...

  6. Base64转Blob

    最近碰见一个问题,a链接的href为base64,但在chrome下载时显示下载失败,经查询,base64过大会在某些浏览器上下载失败(如新版chrome),解决方法是将base64转为blob fu ...

  7. golang test 单元测试

    golang自家的单元测试做的很好了,自需要"文件名_test.go" 就可以在里面写单元测试,而且go test命令也很强大,可以只运行单个测试函数,在goland 可以点击单元 ...

  8. 20145314郑凯杰 《Java程序设计》实验三 敏捷开发与XP实践实验报告

    20145314郑凯杰 <Java程序设计>实验二 实验报告 实验要求 完成实验.撰写实验报告,实验报告以博客方式发表在博客园,注意实验报告重点是运行结果,遇到的问题(工具查找,安装,使用 ...

  9. java第一周学习总结

    学号20145336 <Java程序设计>第1周学习总结 教材学习内容总结 java是sun推出的一门高级编程语言,现已经成为web开发的首选语言.他分为三种技术架构,j2ee针对web应 ...

  10. 20145231熊梓宏 《网络对抗》 实验8 Web基础

    20145231熊梓宏 <网络对抗> 实验8 Web基础 基础问题回答 ●什么是表单? 表单是一个包含表单元素的区域,表单元素是允许用户在表单中输入信息的元素,表单在网页中主要负责数据采集 ...