共计 6384 个字符,预计需要花费 16 分钟才能阅读完成。
logging 模块
logging 模块是一个较庞大的模块。具有较完备的日志体系。
主要分为:主体 Logger – 处理器 – 格式器logging 为 python 内置模块,无需安装。
导入方式:import logging 即可
日志等级排序 (弱 -> 强)
DEBUG < INFO < WARNING < ERROR < FATAL
DEBUG : 开发调试的一些信息(print 调试。。。)
INFO: 程序运行过程的重要信息(不宜过多)WARNING: 不影响程序运行的小问题,警告一下。记录下来以备以后解决。ERROR: 影响程序, 有点严重。需要处理,不然程序(可能,可能)就挂了。FATAL: 严重影响程序,立刻重新排查,修改代码吧。
logging 体系组件
常用分为:(由外到里的包含关系)
- Logger(主体日志)
最大的容器,里面装 Handler - Handler(处理器类)
里面装 Formatter - Formatter (格式器类)
里面写一些打印信息的格式语法
为了方便说明,接下来,我会把上面的组件“由里到外”讲解。
Formatter(格式器类)
格式器:用来定义一些打印信息的字符串格式化的语法。
初始化一个 格式器:
fmt = '[%(asctime)s] [%(filename)s: %(lineno)d] [%(levelname)s] => %(message)s' # 格式
console_formatter = logging.Formatter(fmt=fmt) # 实例化格式器,并把格式传进来
理解方式:
- 我相信你只有“格式”这行 看不明白,这格式你可以随便搭配的,见下方官方文档有参数大全:
官档格式大全:https://docs.python.org/3.7/l… - 打开官档,你会看见表格的第二列 Format。里面的格式直接原封不动复制过来即可 eg: %(asctime)s
然后,你把这些格式用字符串拼接成自己喜欢的符号格式即可,eg: “ 日期为 => %(asctime)s” - 也许你会疑惑,为什么这种 %(..)s 的格式能被识别。而不是被当作原始字符串???
注意第二行代码,fmt 格式字符串只是 Formatter() 的参数,它里面会自动被解析的。这你就别操心了。 -
我把上例结果贴一下,你可能会看明白些:
>>> [2019-09-10 18:23:19,347] [logging11.py: 15] [WARNING] => 哈哈哈 asctime 是日期 filename 是文件名 lineno 是代码行 levelname 是日志等级名(就是上面说的 INFO WARNING ERROR 之类的)message 是 你要打印的日志信息(下面会讲到,这里先小小埋一个点)
Handler(处理器类)
处理器:用来装载上面说的“格式器”,并处理日志(处理器有很多种,按需选 1 个即可,下面说 2 种常用的):
初始化一个“流处理器”(比较常用):
handler = logging.StreamHandler()
或初始化一个“文件处理器”(源码明确写了,它继承的是 上面的“流处理器”。通常用来日志持久化):
handler = logging.FileHandler('mylog.log', mode='a', encoding='utf-8')
# 不必解释了吧。这 API 语法很熟悉了吧。这不就是我们常用的文件 open 语法么。。。。
装载“格式器”(差点忘了吧。实例化的格式器,还没用呢,就是在这里装载)
handler.setFormatter(fmt=file_formatter)
注意: 虽然 handler 对象就可以用 setLevel()设置日志等级,但我不推荐在这里设置。继续往下看
Logger(主体日志类)
Logger:用来装载“处理器的”。
实例化 Logger 有两种方法:
方法 1:(非共享式创建, 不推荐)
log= logging.Logger(name='my_log', level='INFO')
# name 是给 Logger 起的名
# level 是 日志等级(注意要大写),开篇我们讲到过,WARNING, INFO, ERROR 这些。
方法 2:(Log 池共享式创建,推荐)
log = logging.getLogger(name='console')
# 有则取出,无则创建
# name 如果不传,则取出 root Logger(root Logger 是 logging 默认给我们提供的,我一般不用)
说一下这两种方法的区别:
- 非共享式:即为每次都需要重新创建。从 0 开始配置
- Log 池共享式:从 Log 池取出索引来操作(就相当于函数传索引操作)
你每对取出的 Log 做出配置时,都会映射保存更新到 Log 池 中。
当下次(或其他文件,当然是一个完整的程序)调用 getLogger() 取出的 log,就是之前我们配好的。
装载 “ 处理器 ”:(差点忘了吧,上面定义的 处理器,还没用呢,就是在这里用的):
log.addHandler(handler)
设置日志等级(这步可忽略)
log.setLevel('ERROR')
其实上面我们实例化 Logger 的时候,我们就已经传了一个 level 参数,设置好了 日志等级。所以 log.setLevel() 这个可以不设置(包括前面提到,handler 也有 setLevel)handler.setLevel()
开始输出日志信息,有以下日志等级相对应的 API:
log.debug ("这是一条 调试 日志")
log.info ("这是一条 显示主要信息 日志")
log.warning('这是一条 警告 日志')
log.error ("这是一条 错误 日志")
log.fatal ("这是一条 致命错误 日志")
### 回顾我们前面讲的 Formatter 格式器
我们第一个讲的就是格式器,并说了一下常用格式。其中有个 %(message)s, 它就是占位上面这些 API 里面的参数
eg: log.info('哈哈哈')
%(message)s 格式 占位输出的就是 哈哈哈
还有个 %(levelname)s,它就是占位上面这些 API 的方法名
eg: log.info('xxx')
%(levelname)s 格式占位输出的就是 info
如果你对日志等级与日志的作用感到模糊,你一定要看我接下来的例子!!!!!!!!
开篇时我就提过:日志等级排序(弱 => 强) =>(DEBUG < INFO < WARNING < ERROR < FATAL)你设置了一个日志等级。那么你所用上面 API 对应的等级若“强于或等于”此设定的等级,日志才会被处理
emmmmm, 如果没听懂,就当我放 P 了。。。说的越正式,越不容易理解。我们还是看下面的例子吧~~
日志等级理解的小例子:
log.setLevel('WARNING') 你看我们设置的日志等级是 WARNING
log.debug("这是一条 调试 日志")
# 这个 debug(), 你可以去开篇列的 "日志等级排序" 那里瞅一眼。# debug 比 warning 弱,所以 这条日志是 不会 被处理的。#(白话理解:"我给的界限是 warning, 你一个 debug 等级太低了,问题不严重。不配被记录。")log.info("这是一条 显示主要信息 日志")
# 同理,info 也比 warning 弱,此条日志也 不会 被处理
log.warning('这是一条 警告 日志')
# warning == warning(我前面说了,强于 或 等于)所以此条日志会被处理
log.error("这是一条 错误 日志")
# error 比 warning 强,所以此条日志 会 被处理
log.fatal("这是一条 致命错误 日志")
# fatal 比 warning 强,所以此条日志 会 被处理
# 再白话一下:"你给我的容忍程度是 warning, 而你的这条日志都致命错误了,我肯定处理你啊"
思考!上例我一直说一句话“xxxxx, 此条日志才会被处理”。
那么这个“处理”,到底是处理什么呢???
这时不妨回头看看,上面讲的“处理器”,嗯,没错。这些日志就是“处理器”处理的。
- 你要是定义一个流处理器(logging.StreamHandler), 它就会把日志输出到终端。
- 你要是定义一个文件处理器(logging.FileHandler),它就会自动把日志保存到文件中,做持久化
综合案例:
业务需求如下(随便举个案例,不一定有用):
- 比 DEBUG(强),但又比 WARNING(弱),(不包括 WARNING) 的这类日志,只输出到终端。
这类日志输出格式无要求 - 比 WARNING(强)(包括 WARNING)的这类日志,输出到 终端和文件 各一份。
这类日志输出格式有要求,格式为:[日期] [所在文件名: 代码所在行] [日志级别] => 日志内容
代码如下(自己使用的话,封装一下比较好):
import logging
# 日志等级排序(弱 -> 强):DEBUG < INFO < WARNING < ERROR < FATAL
fmt = '[%(asctime)s] [%(filename)s: %(lineno)d] [%(levelname)s] => %(message)s' # 格式
file_formatter = logging.Formatter(fmt=fmt) # 定义格式器,把格式塞进来
file_handler = logging.FileHandler('mylog.log', mode='a', encoding='utf-8') # 定义文件处理器
file_handler.setFormatter(fmt=file_formatter) # 给文件处理器设置 一个 格式器
file_handler.setLevel('WARNING') # 给此处理器设置 日志等级
console_handler = logging.StreamHandler() # 定义流处理器,用于输出到终端
# StreamHandler 未设置格式器,它会默认给你设置一个 %(message)s,即只有日志内容,没有日期文件名等
console_handler.setLevel('DEBUG') # 给流处理器设置 日志等级
log = logging.getLogger(name='file_log') # log 池中取出一个 log(若没有则新建)
log.addHandler(file_handler) # 添加一个文件处理器(格式化 输出到 文件)log.addHandler(console_handler) # 再添加一个流处理器(无格式 输出到 终端)log.info('我只会输出到终端') # 因为 info 只比 console_handler 设置的 DEBUG 强
log.error('我既会输出到终端,又会输出到文件')
# 因为 error 比 console_handler 设置的 DEBUG 强,同时 error 也比 file_handler 设置的 WARNING 强
运行结果:
终端输出:>> 我只会输出到终端
我既会输出到终端,又会输出到文件
mylog.log 文件中:[2019-09-10 23:54:59,055] [logging11.py: 20] [ERROR] => 我既会输出到终端,又会输出到文件
———————- 华丽分割线 —————-
投机取巧方式(不推荐,这里开始往后,可不看)
这种方式只方便了一点点,但不灵活。
前面我们花了好大力气,
- 先是定义了一个 格式器
- 又是定义了一个 流处理器 和 文件处理器
- Logger 池中,实例化一个 Logger
- 并且把他们各种拼装,设置日志等级。等操作(虽然看起来很多。其实你捋通了。真的不复杂)
其实 logging 体系中,有一个默认初始的 Logger,叫做 root Logger.
我们不需要实例化它,也不需要实例化 ” 格式器 ”,也不需要实例化控制器。
一行 API 就可以使用它 (默认是输出到终端的):
import logging
fmt = '[%(asctime)s] [%(filename)s: %(lineno)d] [%(levelname)s] => %(message)s' # 格式
logging.basicConfig( # 默认使用的就是 root Logger
level='DEBUG', # 设置日志等级为 DEBUG
format=fmt, # 设置格式
)
logging.info('我只会输出到终端')
运行结果:>> [2019-09-11 00:15:55,219] [logging11.py: 30] [INFO] => 我只会输出到终端
如果想输出到文件,那么只需加 filename 和 filemode 两个参数即可:
import logging
fmt = '[%(asctime)s] [%(filename)s: %(lineno)d] [%(levelname)s] => %(message)s' # 格式
logging.basicConfig(
level='DEBUG',
format=fmt,
filename='mylog.log', # 文件名
filemode='a' # 文件操作符
)
运行结果:mylog.log 文件:
[2019-09-11 00:20:22,396] [logging11.py: 32] [INFO] => ��ֻ��������ն�
但你发现没,往文件里面输出乱码了,用耳朵都能想出来,我们没有配置 encoding。。。
但是,我告诉你,basicConfig() 是没有 encoding 参数的。那咋整??
但它有个参数叫做 handlers,handlers 熟悉吧,没错就是我们上面讲的 “ 处理器 ”, 复数说明可以传多个
logging.basicConfig(
level='DEBUG',
format=fmt,
handlers=[logging.FileHandler(filename='mylog.log',mode='a',encoding='utf-8') ]
# 看这里这个处理器的定义方法,和之前讲过的一模一样。在这里我们可以配 encoding
)
# 这样就不会乱码了
Note: 以上就是用 logging.baseConfig() 简单日志实现
说了它是投机取巧,因为除了文件乱码有问题之外,它还欠缺灵活性。
比如你想想对不同级别的日志,用不同格式输出出来。这时你单用 basicConfig 一行是搞不定的。
所以还是推荐用 getLogger()那套组合。
结束语
logging 模块其实还有很多很多功能:
过滤器:(其实还有个这个组件,但我没用过,就没说)
格式器:(前面给了官档大全,里面还有根据进程、线程的 (PID,TID, tName,pName) 等格式来输出日志。)
控制器:(我只说了 stream 和 file), 其实还有很多,它们都在 logging.handlers 模块下:
from logging.handlers import (
RotatingFileHandler, # 通过设置文件大小阈值,超出这个阈值,就会将日志转存新文件
TimedRotatingFileHandler, # 设置时间间隔,每过这个间隔,就会将日志转存新文件
HTTPHandler, # 通过 HTTP 协议将日志输出到远程服务器,(只支持 GET 和 POST)SMTPHandler, # 通过 SMTP 协议,将日志输出到远程邮箱了
SocketHandler, # 通过 TCP 协议发送到远程服务器。。。DatagramHandler, # 通过 UDP 协议发送到远程服务器。。。QueueHandler, # 发到队列中(如果想发 RabbitMQ 之类的,可以去 github 找别人写的成品))
# 这些用法也很简单,看官档,或者用 Pycharm ctrl+ 左键点进源码,看一下__init__()参数实例化即可
# 实例化后,用 xxx.addHandler() 添加到 logger 即可使用(和前面讲的 file 和 stream 用法一样)
还可做成各类型配置文件使用:https://docs.python.org/3/lib…
官档案例大全:https://docs.python.org/3/how…