真实搭建父子logger和多个Handler的组合,验证消息在propagate链条上真实经过哪几层level过滤,以及root logger自己的setLevel为什么管不住子logger传播来的消息。
代码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")
buf_root内容: DEBUG: this is a debug message from child ← 真实出现了,尽管root.setLevel是WARNING root.level = 30 (WARNING)
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判断。
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模块跑出来的原始结果。
logging