好的程序开发,往往会兼顾到日志输出的需求,以便给用户提供必要的日志信息便于确认程序运行状态、排错等等。这些日志一般包括程序的正常运行日志、访问日志、错误日志、数据保存日志等类型。在python中logging模块提供了标准的日志接口,可以满足我们对日志输出的各种需求,下面一一详述。
业内常用的日志有五个级别,分别是:debug,info,warning,error和critical,其中debug级别最低,critical级别最高,级别越低,打印的日志等级越多。各个级别的定义如下:
Level | When it’s used |
---|---|
DEBUG | Detailed information, typically of interest only when diagnosing problems. |
INFO | Confirmation that things are working as expected. |
WARNING | An indication that something unexpected happened, or indicative of some problem in the near future(e.g. ‘disk spack low’). The software is still working as expected. |
ERROR | Due to a more serious problem, the software has not been able to perform some function. |
CRITICAL | A serious error, indicating that the program itself may be unable continue running. |
默认的级别是warning,只有在warning级别和以上级别的日志才会输出,这样可以避免日志过多的问题。当然这个默认的级别是可以自定义修改的,下文详述。
最简单的日志输出做输出到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,该级别以下的日志没有输出。
日志输出到文件具有持久化保存的功能,便于随时回溯查看。通过basicConfig可以定义日志输出的参数。关键参数如下:
关键字 | 描述 |
---|---|
filename | 创建一个FileHandler,使用指定的文件名,而不是使用StreamHandler。 |
filemode | 如果指明了文件名,指明打开文件的模式(如果没有指明filemode,默认为‘a’) |
format | handler使用指明的格式化字符串 |
datefmt | 使用指明的日期/时间格式 |
level | 指明根logger的级别 |
stream | 使用指明的流来初始化StreamHandler。该参数与‘filename’不兼容,如果两个都有,‘stream’被忽略。 |
当然了,这个日志级别是可以灵活自定义的。
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]')
前面的示例中,日志的输出没有什么可读性而言,这里可以通过format来增加时间等字段,提高可读性。常用的format相关的参数如下:
参数 | 说明 |
---|---|
%(levelno)s | 数字形式的日志级别 |
%(levelname)s | 文本形式的日志级别 |
%(pathname)s | 调用日志输出函数的模块的完整路径名,可能没有 |
%(filename)s | 调用日志输出函数的模块的文件名 |
%(module)s | 调用日志输出函数的模块名 |
%(funcName)s | 调用日志输出函数的函数名 |
%(lineno)d | 调用日志输出函数的语句所在的代码行 |
%(created)f | 当前时间,用UNIX标准的表示时间的浮点数表示 |
%(relativeCreated)d | 输出日志信息时的,自Logger创建以来的毫秒数 |
%(asctime)s | 字符串形式的当前时间。默认格式是“2018-04-25 10:39:50, 896”。逗号后面的是毫秒 |
%(thread)d | 线程ID。可能没有 |
%(threadName)s | 线程名。可能没有 |
%(process)d | 进程ID。可能没有 |
%(message)s | 用户输出的消息 |
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-04-25 10:54:22,479 [INFO] p_logging service started
2018-04-25 10:54:22,479 [WARNING] p_logging memory leak
2018-04-25 10:54:22,479 [ERROR] p_logging can not started
2018-04-25 10:54:22,479 [CRITICAL] p_logging fatal error
因为示例程序的文件名为p_logging.py,所以这里日志输出的模块名显示为p_logging。还可以通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')
输出:
04/25/2018 10:56:41 [INFO] p_logging service started
04/25/2018 10:56:41 [WARNING] p_logging memory leak
04/25/2018 10:56:41 [ERROR] p_logging can not started
04/25/2018 10:56:41 [CRITICAL] p_logging fatal error
前文讲述了日志输出的一些基本用法,复杂的用法还需要进阶学习:
python使用logging模块记录日志涉及四个主要类:
logger:记录器,提供了应用程序可以直接使用的接口。
handler:处理器,将(logger创建的)日志记录发送到合适的目的输出。必须有handler才能捕获输出的日志。
filter:过滤器,对日志进行过滤来决定输出哪条日志记录。
formatter:格式化器,决定日志记录的最终输出格式。
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的个数。
handler对象负责发送相关的信息到指定目的地。Python的日志系统有多种Handler可以使用。有些Handler可以把信息输出到控制台,有些Logger可以把信息输出到文件,还有些 Handler可以把信息发送到网络上。如果觉得不够用,还可以编写自己的Handler。可以通过addHandler()方法添加多个多handler 。
Handler处理器类型有很多种,比较常用的有三个,StreamHandler,FileHandler,NullHandler,详情可以访问Python logging.handlers
创建StreamHandler之后,可以通过使用以下方法设置日志级别,设置格式化器Formatter,增加或删除过滤器Filter。
创建方法: sh = logging.StreamHandler(stream=None)
创建方法: fh = logging.FileHandler(filename, mode='a', encoding=None, delay=False)
NullHandler类位于核心logging包,不做任何的格式化或者输出。
本质上它是个“什么都不做”的handler,由库开发者使用。
下面讲解handler的重要知识点:
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级别的日志。
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')
输出test.log的内容:
2018-04-25 11:53:28,231 [INFO] info log --p_logging
2018-04-25 11:53:28,232 [ERROR] error log --p_logging
它的构造函数是: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-04-25 11:08:26,253 [INFO] info log --log
time.log 文件 2018-04-25 11:08:26,254 [ERROR] error log --log
上述示例程序中由于一行日志的输出刚好达到50字节的日志文件切割临界值,所以两行log输出到了两个日志文件中,且越早输出的日志会写到了后缀越大的日志文件中(最新的日志文件,可认为后缀为0)
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导致达到日志滚动的时间阈值,所以滚动生成了一个空日志文件
使用Formatter对象设置日志信息最后的规则、结构和内容,默认的时间格式为%Y-%m-%d %H:%M:%S。
创建方法: formatter = logging.Formatter(fmt=None, datefmt=None)
其中,fmt是消息的格式化字符串,datefmt是日期字符串。如果不指明fmt,将使用’%(message)s’。如果不指明datefmt,将使用ISO8601日期格式。
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中的日志级别设置
参考:http://www.cnblogs.com/linupython/p/8456431.html