在上一篇文章 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")
输出
msg
这里我没有给 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")
输出
- child info
- INFO:child:child info
代码中明明只记录了一次日志, 却输出了两次, 而且两次的日志格式不一样. 这是因为 child 这个记录器添加了一个叫 console_handler 的处理器, 而 root 根记录器默认也带有自己的处理器(也是 StreamHandler 实例)
print(root.handlers) # [ (NOTSET)>]
根据 python 中日志模块的处理机制, 子记录器记录的消息会自动传播给父级记录器的关联的处理器. 所以在这个例子中, child 记录的消息除了会发给自己的 handler 外, 还是传播给 root 记录器的 handler, 因此最终输出了两次, 流程图如下
logging-flow.PNG
如果不希望子记录器记录的消息传播给父级记录器, 可以设置记录器的属性 propagate 为 False, 关闭传播.
child.propagate = False
如此一来, 最终输出到终端的日志就只有 child 自己的处理器输出的记录
child info
配置处理器的最佳实践是给顶级记录器配置处理器, 再根据需要创建子记录器, 因为记录最终都会传播给父记录器
- import logging
- parent = logging.getLogger("parent")
- parent.setLevel(logging.DEBUG)
- parent.addHandler(logging.StreamHandler())
- # 不需要给子记录器单独配置 handler
- child = logging.getLogger("parent.child")
- child.info("msg")
对于日志的流程处理, python 官方文档画了一张更为细致的流程图, 可以参考
logging_flow.PNG
第一次看估计有点晕, 但先看我画的这张图再来看这张图, 你就能懂了, 为了简化我省去了过滤器以及不断循环查找父级记录器的这个流程.
3, 为什么我的 pycharm 中输出的日志是红色?
不知道你的 pycharm 输出的日志不管是 info 信息还是 error 信息, 反正都是红色, 一看以为整屏都是错误.
把下面代码放在 Pycharm 运行看效果:
- import logging
- logging.basicConfig(level=logging.DEBUG)
- logging.info("hello")
这是因为使用 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
初始化这个 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")
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)
5, 为什么我日志配置后不生效?
可能跟程序的加载顺序有关, 看个例子
- import logging
- logger = logging.getLogger()
- handler = logging.StreamHandler()
- logger.addHandler(handler)
- logger.info("hello1")
- logger.setLevel(logging.INFO)
- logger.info("hello2")
像上面的代码最后只输出了 hello2, 不过实际场景中, 代码没这么简单, 通常是在 a 模块中的某个函数中初始化日志框架配置, 在 b 模块外层创建了名字叫 logger_b 的记录器, 然后在 a 中导入 b 模块时, 这时候日志配置还没初始化, 最后导致 logger_b 的配置就成了默认配置. 所以有可能出现日志不生效的情况.
因此最佳实践是能尽早初始化日志配置就尽早提前.
来源: http://developer.51cto.com/art/202201/699271.htm