logging模块是Python内置的标准模块,主要用于输出运行日志。
简单应用
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+++
可见,默认情况下Python的logging模块将日志打印到了标准输出中,且只显示了大于等于WARNING级别的日志,这说明默认的日志级别设置为WARNING(日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET),默认的日志格式为 日志级别:Logger名称:用户输出消息
灵活配置日志级别,日志格式,输出位置
import logging logging.basicConfig(level=logging.DEBUG, #级别为DEBUG,所以五条都打出来了 format='%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s %(message)s', #依次为时间、文件名、行号、日志级别、内容 datefmt='%a, %d %b %Y %H:%M:%S', #日期时间的格式 filename='test.log', #日志存储的指定文件 filemode='w') #默认是a追加,w是写入 logging.debug('+++debug+++') logging.info('+++info+++') logging.warning('+++warning+++') logging.error('+++error+++') logging.critical('+++critical+++')
test.log内容: Tue, 15 Jan 2019 20:20:06 logging_lesson.py [line:9] DEBUG +++debug+++ Tue, 15 Jan 2019 20:20:06 logging_lesson.py [line:10] INFO +++info+++ Tue, 15 Jan 2019 20:20:06 logging_lesson.py [line:11] WARNING +++warning+++ Tue, 15 Jan 2019 20:20:06 logging_lesson.py [line:12] ERROR +++error+++ Tue, 15 Jan 2019 20:20:06 logging_lesson.py [line:13] CRITICAL +++critical+++
可见在logging.basicConfig()函数中可通过具体参数来更改logging模块默认行为,可用参数有:
filename:用指定的文件名创建FiledHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。
filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”追加,还可指定为“w”写入。
format:指定handler使用的日志显示格式。
datefmt:指定日期时间格式。
level:设置rootlogger(后边会讲解具体概念)的日志级别
stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件(f=open('test.log','w')),默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。
format参数中可能用到的格式化串:
%(name)s Logger的名字
%(levelno)s 数字形式的日志级别
%(levelname)s 文本形式的日志级别
%(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
%(filename)s 调用日志输出函数的模块的文件名
%(module)s 调用日志输出函数的模块名
%(funcName)s 调用日志输出函数的函数名
%(lineno)d 调用日志输出函数的语句所在的代码行
%(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
%(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
%(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
%(thread)d 线程ID。可能没有
%(threadName)s 线程名。可能没有
%(process)d 进程ID。可能没有
%(message)s用户输出的消息
logger对象
import logging logger = logging.getLogger() #返回一个logger对象 fh = logging.FileHandler('test2_log') #创建一个handler,用于写入日志文件 ch = logging.StreamHandler() #再创建一个handler,用于输出到控制台 fm = logging.Formatter('%(asctime)s %(message)s') #创建一个格式,将用于上面的两个handler fh .setFormatter(fm) #两个handler设定格式 ch.setFormatter(fm) logger.addHandler(fh) #两个handler都添加进logger对象 logger.addHandler(ch) logger.setLevel('DEBUG') #设置logger的日志级别 logger.debug('+++debug+++') #正式调用logger对象打印日志 logger.info('+++info+++') logger.warning('+++warning+++') logger.error('+++error+++') logger.critical('+++critical+++')
结果:控制台输出和test2_log文件中的内容都是:
2019-01-15 23:29:18,465 +++debug+++
2019-01-15 23:29:18,465 +++info+++
2019-01-15 23:29:18,465 +++warning+++
2019-01-15 23:29:18,465 +++error+++
2019-01-15 23:29:18,465 +++critical+++
如上图,fh和ch作为两个handler对象(一个负责写入文件,一个负责控制台输出),都"吃"掉了fm这个格式对象,又都被最后负责打印日志的logger对象"吃"掉。
使用logger对象是我们常用的方式,Logger是一个树形层级结构, logger = logging.getLogger()返回一个默认的Logger也即root Logger,并应用默认的日志级别、Handler和Formatter设置。
当然也可以通过Logger.setLevel(lel)指定最低的日志级别,可用的日志级别有logging.DEBUG、logging.INFO、logging.WARNING、logging.ERROR、logging.CRITICAL。
但是有两个注意点:
1.只要logging.getLogger(name)中名称参数name相同则返回的Logger实例就是同一个,且仅有一个
import logging logger1 = logging.getLogger('mylogger') logger1.setLevel(logging.DEBUG) logger2 = logging.getLogger('mylogger') logger2.setLevel(logging.INFO) fh = logging.FileHandler('test1_log') ch = logging.StreamHandler() logger1.addHandler(fh) logger1.addHandler(ch) logger2.addHandler(fh) logger2.addHandler(ch) logger1.debug('logger1+++debug+++') logger1.info('logger1+++info+++') logger1.warning('logger1+++warning+++') logger1.error('logger1+++error+++') logger1.critical('logger1+++critical+++') logger2.debug('logger2+++debug+++') logger2.info('logger2+++info+++') logger2.warning('logger2+++warning+++') logger2.error('logger2+++error+++') logger2.critical('logger2+++critical+++')
结果:控制台输出和test1_log文件中的内容都是:
logger1+++info+++ #logger1虽然设置了日志级别为DEBUG,但遵从logger2设置的日志级别INFO,因为两者对应的是同一个Logger实例
logger1+++warning+++
logger1+++error+++
logger1+++critical+++
logger2+++info+++
logger2+++warning+++
logger2+++error+++
logger2+++critical+++
logger1.setLevel(logging.DEBUG)将logger1的日志级别设置为了DEBUG,为何显示的时候没有显示出DEBUG级别的日志信息,而是从INFO级别的日志开始显示呢?
原来logger1和logger2对应的是同一个Logger实例,只要logging.getLogger(name)中名称参数name相同则返回的Logger实例就是同一个,且仅有一个,就是说name与Logger实例一一对应。在logger2实例中通过logger2.setLevel(logging.INFO)设置mylogger的日志级别为logging.INFO,所以最后logger1的输出遵从了后来设置的日志级别。
所以,创建Logger时,如果是需要两个实例对象,就要在logging.getLogger(name)中把name参数区分开。
2."子实例"的handler进行处理时,也会传递给所有的"父实例"Logger处理,所以子实例的处理会被执行多次。
import logging logger = logging.getLogger() logger2 = logging.getLogger('mylogger2') logger2.setLevel(logging.INFO) ch = logging.StreamHandler() logger.addHandler(ch) logger2.addHandler(ch) logger.debug('logger+++debug+++') logger.info('logger+++info+++') logger.warning('logger+++warning+++') logger.error('logger+++error+++') logger.critical('logger+++critical+++') logger2.debug('logger2+++debug+++') logger2.info('logger2+++info+++') logger2.warning('logger2+++warning+++') logger2.error('logger2+++error+++') logger2.critical('logger2+++critical+++')
结果:
logger+++warning+++
logger+++error+++
logger+++critical+++
logger2+++info+++ #logger2执行了两次
logger2+++info+++
logger2+++warning+++
logger2+++warning+++
logger2+++error+++
logger2+++error+++
logger2+++critical+++
logger2+++critical+++
通过logger = logging.getLogger()创建了root Logger,而logger2 = logging.getLogger('mylogger2')创建了root Logger的“子实例”logger2。而孩子,孙子,重孙……的handler进行处理时,也会传递给所有祖先的Logger处理,所以logger2被执行了两边。
也就是说,子实例工作的时候会依次去找他的父辈(树结构,可能有好几层父辈)有没有在工作,如果有一个父辈在工作,子实例就多工作一次。
上述例子中,如果把logger.addHandler(ch)注释掉,logger2就只执行一遍。