本文承接上一篇分为如下几个部分日志输出
捕获异常
配置共享
配置到文件
使用规范
参考资料
日志输出
我们之前都是将日志输出到控制台,而实际项目中常常需要将日志存储为文件,我们直接看代码
import logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s%(message)s',
datefmt='%a,%d%b %Y %H:%M:%S +0000',
filename='my.log')
logging.info('this is a info')
可以看到,只要在logging.basicConfig中加入filename参数,就不会再在控制台中输出日志,而是会将所有日志存入my.log文件中。
需要注意的一点是:上面日志存储方式是追加的,也就是说,上面这个代码连续运行两次,文件中是会有两行日志的。
如果需要改变输出形式,需要调整参数如下
import logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s%(message)s',
datefmt='%a,%d%b %Y %H:%M:%S +0000',
filename='my.log', filemode='w')
logging.info('this is a info')
其他读写格式可以参考文件读写格式。
指定日志输出也可以不在logging.basicConfig中配置,而是单独设置
import logging
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)
handler = logging.FileHandler('my.log')
formatter = logging.Formatter('%(asctime)s%(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.critical('Critical 50')
logger.error('Error 40')
logger.warning('Warning 30')
logger.info('Info 20')
logger.debug('Debug 10')
这可以达到和刚才相同的效果。我们可以看到,不仅输出方式可以单独配置,而且format也可以单独配置。
配置它们的流程是配置好的format设置到handler中
配置好的handler添加到logger中
添加多个输出端,且不同输出端输出内容不一样(level不同,format不同)
import logging
import sys
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.DEBUG) # 设置基本level
# 输出到控制台,不设置format
handler_stream = logging.StreamHandler(sys.stdout)
handler_stream.setLevel(level=logging.WARN) # 更改level
logger.addHandler(handler_stream)
# 输出到文件,继承基础level
handler_file = logging.FileHandler('my.log', 'w')
formatter = logging.Formatter('%(asctime)s%(message)s')
handler_file.setFormatter(formatter) # 设置format
logger.addHandler(handler_file)
logger.critical('Critical 50')
logger.error('Error 40')
logger.warning('Warning 30')
logger.info('Info 20')
logger.debug('Debug 10')
控制台输出结果为
Critical 50
Error 40
Warning 30
my.log文件输出结果为
2018-07-01 21:03:27,164 Critical 50
2018-07-01 21:03:27,165 Error 40
2018-07-01 21:03:27,167 Warning 30
2018-07-01 21:03:27,171 Info 20
2018-07-01 21:03:27,172 Debug 10
这里需要注意一点,基础level要设置比较低一些,后面handler设置的level只有比基础高才有效。
logging模块还提供了许多其他的日志输出形式,详情可以见官网
捕获异常
我们希望将程序的报错信息记录到log文件中
import logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s%(message)s',
datefmt='%a,%d%b %Y %H:%M:%S +0000',
filename='my.log')
logging.info('this is a info')
try:
do
except Exception:
logging.error('There are something wrong', exc_info=True)
logging.info('continue')
输出到文件的结果如下
Sun, 01 Jul 2018 21:10:43 +0000 this is a info
Sun, 01 Jul 2018 21:10:53 +0000 this is a info
Sun, 01 Jul 2018 21:10:53 +0000 There are something wrong
Traceback (most recent call last):
File "learn.py", line 9, in
do
NameError: name 'do' is not defined
Sun, 01 Jul 2018 21:10:53 +0000 continue
其中exc_info的作用就是在日志中包含具体的报错信息,默认是False不包含。
配置共享
在写一个项目时,需要编写多个文件,每个文件内都要设置相同格式的日志输出,如果每个文件都重新配置一遍就太麻烦了,logging模块为我们提供了一个简单的方法。
比如在main.py文件中编写如下代码
import logging
import a
logging.basicConfig(level=logging.INFO,
format='%(asctime)s%(message)s',
datefmt='%a,%d%b %Y %H:%M:%S +0000')
logger = logging.getLogger('mainlogger')
logger.info('main file log')
a.run()
在a.py文件中编写如下代码
import logging
logger = logging.getLogger('mainlogger.a')
def run():
logger.info('a file log')
运行main.py文件,输出结果如下
Sun, 01 Jul 2018 21:19:57 +0000 main file log
Sun, 01 Jul 2018 21:19:57 +0000 a file log
我们可以看到,在a.py文件中只是将logger名称设置为以main.py文件中的logger名称开头,就可以继承main.py文件中的配置了,甚至不需要在a.py文件中导入main.py文件。
配置到文件
我们不仅可以通过python代码进行logging配置,而且可以通过写一个yaml文件进行配置,每次需要用logging时只要调用这个文件就配置完成。
config.yaml文件内容如下
version: 1
formatters:
simple:
format: "%(message)s"
more:
format: "%(asctime)s - %(levelname)s - %(message)s"
handlers:
console:
class : logging.StreamHandler
formatter: simple
level: INFO
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: more
level: DEBUG
filename: debug.log
loggers:
mainlogger:
level: DEBUG
handlers: [console, file]
root:
level: DEBUG
handlers: [console]
main.py文件中编写代码如下
import logging
import logging.config
import yaml
import a
with open('config.yaml', 'r', encoding='utf-8') as f:
config = yaml.load(f)
logging.config.dictConfig(config)
logging.info('main file log')
a.run()
a.py文件中编写代码如下
import logging
logger = logging.getLogger('mainlogger')
def run():
logger.info('a file log')
运行main.py结果在控制台中输出如下
a file log
INFO:mainlogger:a file log
在debug.log文件中输出如下
2018-07-01 21:45:37,673 - INFO - a file log
对于这样的输出结果,我们回到yaml文件理一下思路,对这个文件从下往上看首先对于不同的logger名称,如果名称是mainlogger,则使用[console, file]这两个handler,如果未指定(使用logging.info进行输出)则对应root只按照console输出
对于不同的handler,名称为console的handler使用logging.StreamHandler输出到控制台,调用simple的format,而file则输出到文件,使用more的format
对于不同的format格式则在最上面的formatters中定义
通过文件配置的更多内容可以参考官网
使用规范
1.输出字符串的规范
import logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s%(levelname)s%(message)s')
info = 'apple'
# bad
logging.info('this is a{}'.format(info))
# good
logging.info('this is a%s', info)
二者输出皆为
2018-07-01 21:56:12,969 INFO this is a apple
2.异常处理规范
import logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s%(message)s',
datefmt='%a,%d%b %Y %H:%M:%S +0000')
logging.info('this is a info')
try:
do
except Exception as e:
# bad
logging.error('There are something wrong:%s', e)
# good
logging.error('There are something wrong', exc_info=True)
# good
logging.exception('There are something wrong')
logging.info('continue')
第一种输出异常结果会是
Sun, 01 Jul 2018 21:58:52 +0000 There are something wrong: name 'do' is not defined
后两种输出异常结果会是
Sun, 01 Jul 2018 21:58:52 +0000 There are something wrong
Traceback (most recent call last):
File "learn.py", line 8, in
do
NameError: name 'do' is not defined
3.日志级别设置
这里列出这篇文章中认为的日志级别使用方法最细微的信息记录到debug中,这个级别就是用来debug的,看程序在哪一次迭代中发生了错误,比如每次循环都输出一些东西用debug级别
info级别用于routines,也就是输出start finish 状态改变等信息
warn输出一些相对重要,但是不是程序bug的信息,比如输入了错误的密码,或者连接较慢
error输出程序bug,打印异常信息
critical用于处理一些非常糟糕的事情,比如内存溢出、磁盘已满,这个一般较少使用
4.建议使用RotatingFileHandler而不是FileHandler
因为日志文件写入是不断增加的过程,如果用FileHandler,久了日志文件会非常大,不利于读写和管理。
用RotatingFileHandler设置日志文件大小,比如10M。则如果日志文件达到10M,就会自动将这个文件重命名,然后创建一个新的日志文件继续写入
也可以使用TimedRotatingFileHandler,这是根据时间,每隔一段时间会自动创建新的日志文件
5.get_logger位置
在一个被import的文件中get_logger的代码要放在函数里面,因为如果放在函数外面,在主文件import这个文件时,就会运行这个初始化。而在import的时候,主文件一般还没有读入yaml文件,因此事先初始化的log就不会正常工作。
参考资料
专栏信息
专栏目录:目录