bigywater.tech
Python · 36

logging 系统:Logger、Handler、Formatter 如何协作

真实搭建父子logger和多个Handler的组合,验证消息在propagate链条上真实经过哪几层level过滤,以及root logger自己的setLevel为什么管不住子logger传播来的消息。

祖先logger的setLevel
真实不过滤传播来的消息,只有祖先Handler的level才过滤
同logger的多个Handler
真实各自level独立生效
Formatter
真实只管渲染格式,不参与过滤判断

做的事:子logger和父logger(root)各配一个Handler,设不同的level

代码root = logging.getLogger() root.setLevel(logging.WARNING) h_root = logging.StreamHandler(buf_root) h_root.setLevel(logging.DEBUG) # Handler本身level设得很低 root.addHandler(h_root) child = logging.getLogger("app.db") child.setLevel(logging.DEBUG) # 子logger level设得很低 child.debug("this is a debug message from child")

验证:root.setLevel(WARNING)真实没有挡住子logger传播过来的DEBUG消息

实测 · 本机执行输出
buf_root内容:
DEBUG: this is a debug message from child   ← 真实出现了,尽管root.setLevel是WARNING

root.level = 30 (WARNING)
这是本文认为logging模块最容易被误解的真实机制:Logger.setLevel()这个"关卡"只在消息从这个logger自己产生的那一刻检查一次(child.setLevel(DEBUG)让这条DEBUG消息真实通过了第一道关卡),一旦通过,这条消息会作为一个LogRecord对象沿着logger的父子链条真实向上传播(propagate默认是True),传播路径上遇到的每一个祖先logger,Python真实只检查它们各自挂的Handler的level,完全不会再检查祖先logger自己的setLevel()——换句话说,root.setLevel(WARNING)这个设置真实只对"直接调用root.debug()/root.warning()"这种情况生效,对"别的logger的消息传播到root"完全不起过滤作用。真实的过滤关卡只有两处:产生消息的那个logger自己的level,和沿途每个Handler自己的level。

验证:同一个logger的两个Handler,各自的level真实独立生效

实测 · 本机执行输出
child logger(level=DEBUG)挂了h1(level=INFO),root挂了h2(level=ERROR)
child.debug/.info/.warning/.error 依次调用

h1收到(level=INFO,挡住了debug):
[H1 INFO] app.db: info msg
[H1 WARNING] app.db: warning msg
[H1 ERROR] app.db: error msg

h2收到(level=ERROR,只留下error,即使这条消息是从child传播过来的):
[H2 ERROR] error msg

真实的过滤链条是:一条消息先过child自己的level(DEBUG,全部放行)→ 交给child自己挂的h1,h1.level=INFO真实挡掉了debug这一条 → 消息(不管有没有被h1处理)继续沿propagate链条往上传给root → root自己没有setLevel相关的检查(如前一节验证的),直接交给root挂的h2,h2.level=ERROR真实只留下了error这一条。同一条消息真实可以被多个Handler分别按各自的level独立决定要不要输出、输出成什么格式——这正是logging设计成"Logger管产生和传播,Handler管过滤和落地"的价值:一个应用可以真实做到"控制台只打WARNING以上,但写到文件的Handler记录全部DEBUG",两者互不影响,不需要在业务代码里写任何if判断。

验证:Formatter真实只决定"长什么样",不参与过滤

实测 · 本机执行输出
h1.setFormatter(Formatter("[H1 %(levelname)s] %(name)s: %(message)s"))
h2.setFormatter(Formatter("[H2 %(levelname)s] %(message)s"))

同一条error消息,经过两个Handler,真实输出成两种完全不同的格式:
h1: [H1 ERROR] app.db: error msg
h2: [H2 ERROR] error msg

真实验证了Formatter在这套体系里的角色边界很窄:它不参与"这条消息该不该被处理"的判断(那是Logger.level和Handler.level的事),只负责把已经决定要输出的LogRecord对象(真实携带着levelname、name、message等字段)渲染成最终的文本行。同一条消息、同一次调用,真实可以在不同Handler那里呈现出完全不同的格式——控制台Handler可能只要精简的一行,写到文件的Handler可能要带上完整的时间戳和模块名,这些格式差异真实完全通过各自Handler绑定不同的Formatter实例来实现,跟Logger本身的逻辑没有任何耦合。

分步演示:一条日志消息真实经过的三层关卡

每一步都是本文上面用真实的logging模块跑出来的原始结果。

消息传播链条上,谁的level真实生效、谁的不生效
这一步新验证的环节

    本文用到的工具

    • 本机 Python 3.14.7,标准库 logging
    京ICP备2026067502号-1京公网安备11010502063487号