在上一篇文章1万字详解 python logging日志模块 中,深入浅出的讲解了日志的基本原理与用法。但还有一些内容并没有涉及到,所以这篇文章作为上一篇文章的补充。
希望这两篇文章能帮助你完全理解日志模块的使用,在项目中对日志的运用游刃有余。上一篇还没看的建议先阅读上一篇
1、为什么子记录器不需要设置日志等级也可以输出?
如果未在记录器上显式设置级别,则使用其父记录器的级别作为其有效级别。如果父记录器也没有设置级别,则依此类推,搜索父级的父级,直到找到明确设置了级别的记录器。根记录器默认下为 WARNING 级别
import logging
parent = logging.getLogger("parent")
parent.setLevel(logging.INFO)
parent.addHandler(logging.StreamHandler())
child = logging.getLogger("parent.child")
child.info("msg")
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
输出
msg
- 1.
这里我没有给child设置日志等级,他会从父记录器查找日志级别,所以child也可以输出info级别的日志。关于记录器的继承关系可以参考第一篇文章
2、为什么有时候日志会输出两次?
看下面例子:
import logging
# 初始化日志,并设置日志级别(为root设置为DEBUG级别,关联StreamHandler,设置BASIC_FORMAT格式)
logging.basicConfig(level=logging.DEBUG)
# 定义root记录器
root = logging.getLogger()
# 定义child记录器
child = logging.getLogger("child")
console_handler = logging.StreamHandler()
# 给child绑定处理器
child.addHandler(console_handler)
# 记录一条info日志
child.info("child info")
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
输出
child info
INFO:child:child info
- 1.
- 2.
代码中明明只记录了一次日志,却输出了两次,而且两次的日志格式不一样。这是因为 child 这个记录器添加了一个叫console_handler的处理器, 而root根记录器默认也带有自己的处理器(也是StreamHandler实例)
print(root.handlers) # [<StreamHandler <stderr> (NOTSET)>]
- 1.
根据python中日志模块的处理机制,子记录器记录的消息会自动传播给父级记录器的关联的处理器。所以在这个例子中,child记录的消息除了会发给自己的handler外,还是传播给root记录器的handler,因此最终输出了两次,流程图如下
logging-flow.png
如果不希望子记录器记录的消息传播给父级记录器,可以设置记录器的属性propagate为False,关闭传播。
child.propagate = False
- 1.
如此一来,最终输出到终端的日志就只有child自己的处理器输出的记录
child info
- 1.
配置处理器的最佳实践是给顶级记录器配置处理器,再根据需要创建子记录器, 因为记录最终都会传播给父记录器
import logging
parent = logging.getLogger("parent")
parent.setLevel(logging.DEBUG)
parent.addHandler(logging.StreamHandler())
# 不需要给子记录器单独配置handler
child = logging.getLogger("parent.child")
child.info("msg")
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
对于日志的流程处理,python官方文档画了一张更为细致的流程图,可以参考
logging_flow.png
第一次看估计有点晕,但先看我画的这张图再来看这张图,你就能懂了,为了简化我省去了过滤器以及不断循环查找父级记录器的这个流程。
3、 为什么我的pycharm中输出的日志是红色?
不知道你的pycharm输出的日志不管是info信息还是error信息,反正都是红色,一看以为整屏都是错误。
把下面代码放在Pycharm运行看效果:
import logging
logging.basicConfig(level=logging.DEBUG)
logging.info("hello")
- 1.
- 2.
- 3.
这是因为使用root记录器记录日志时,默认配置的handler是一个StreamHandler。
我们打开StreamHandler的源码
class StreamHandler(Handler):
"""
A handler class which writes logging records, appropriately formatted,
to a stream. Note that this class does not close the stream, as
sys.stdout or sys.stderr may be used.
"""
terminator = '\n'
def __init__(self, stream=None):
"""
Initialize the handler.
If stream is not specified, sys.stderr is used.
"""
Handler.__init__(self)
if stream is None:
stream = sys.stderr
self.stream = stream
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
初始化这个Handler时,会接收一个stream的参数,如果不传,默认就使用的系统标准错误流(sys.stderr)输出,pycharm对错误流输出的字体样式做了红色渲染,如果换成 sys.stdout 输出的就不再红色了。
import logging
import sys
handler = logging.StreamHandler(stream=sys.stdout)
logging.basicConfig(level=logging.DEBUG, handlers=[handler])
# 或者指定stream参数
# logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
logging.info("hello")
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
4、怎么生成以日期时间命名的日志?
实际应用中,我们会对日志进行归档存储,每天生成一份日志,如果哪天出了问题,也方便定位,直接找到当天的日志文件就可以分析。我们只需要给logger添加一个TimedRotatingFileHandler处理器就行。
file_handler = TimedRotatingFileHandler(‘'logs/api.log'),
when="D", interval=1, backupCount=10,
encoding="UTF-8", delay=False, utc=True)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
file_handler.setLevel(logging.INFO)
logger.addHandler(file_handler)
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
5、为什么我日志配置后不生效?
可能跟程序的加载顺序有关,看个例子
import logging
logger = logging.getLogger()
handler = logging.StreamHandler()
logger.addHandler(handler)
logger.info("hello1")
logger.setLevel(logging.INFO)
logger.info("hello2")
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
像上面的代码最后只输出了hello2,不过实际场景中,代码没这么简单,通常是在a模块中的某个函数中初始化日志框架配置, 在b模块外层创建了名字叫 logger_b的记录器,然后在a中导入b模块时,这时候日志配置还没初始化,最后导致logger_b的配置就成了默认配置。所以有可能出现日志不生效的情况。
因此最佳实践是能尽早初始化日志配置就尽早提前。