Python logging propagate 为真时为何出现重复日志
来源:17golang原创
时间:2026-09-15 06:39:14 306浏览 收藏
Python 日志出现“一条消息打印两遍”,通常不是 propagate 把同一条记录复制成了两个对象,而是同一个 LogRecord 依次交给了子 logger 和祖先 logger 上的多个 handler。最常见的组合是:业务 logger 自己挂了一个 StreamHandler,同时 propagate=True 又把记录送到 root logger 的 StreamHandler。
- 重复输出先查 handler 的挂载位置,不要先改日志级别。
propagate=True会把记录交给祖先 logger 的 handler,但不会再次按祖先 logger 的 level/filter 过滤。- 集中式日志只保留高位 handler;子 logger 需要独立输出时,配合
propagate=False切断上送。
先判断:到底是哪两个 handler 在输出
logger 的名字按点号形成层级。例如 app.worker 是 app 的子 logger,最终还会接近 root logger。重复日志排查要把“谁产生日志”和“谁真正写出日志”分开看。下面的检查函数不会改变配置,只打印当前 logger 的直接 handler、传播开关和祖先链。
import logging
def inspect_logger(name: str) -> None:
# 只检查配置结构,不发送新的日志记录。
logger = logging.getLogger(name)
print(f"logger={logger.name!r} propagate={logger.propagate}")
print("direct handlers:", [type(item).__name__ for item in logger.handlers])
# 沿父级向上查找,定位可能重复处理同一记录的 handler。
parent = logger.parent
while parent is not None:
print(f"ancestor={parent.name or 'root'} handlers="
f"{[type(item).__name__ for item in parent.handlers]}")
if not parent.propagate:
break
parent = parent.parent
inspect_logger("app.worker")
如果子 logger 和 root 都出现了 StreamHandler,且中间没有 propagate=False,重复输出就有了明确解释。注意 hasHandlers() 只能告诉你“当前链路能找到 handler”,不能替代对每一层 handlers 的逐层检查。
logging 模块默认的日志传播机制是子 Logger 产生的日志事件,会沿着父级 Logger 链路逐层向上传递,每一层父 Logger 只要挂载了 Handler 就会尝试输出这条日志。当 propagate 属性设为 True 时,子 Logger 打印的日志除了会经过自身绑定的 Handler 输出,还会被逐层交给上层所有未设置过 Handler 级别的父 Logger 去执行输出逻辑,最终就会出现同一条日志被多次打印的重复现象。

propagate=True 为什么会把同一条日志送到多处
propagate 的默认值是 True。当 app.worker 记录一条 ERROR 时,先会提供给它自己直接挂载的 handler;如果允许传播,记录还会继续提供给 app、root 等祖先 logger 的 handler,直到传播被切断。这里的“传播”是把记录交给祖先 handler,不是把日志级别再从头计算一次。
| 配置组合 | 可见结果 | 适合场景 |
|---|---|---|
| 子 logger 无 handler,root 有一个 | 通常只输出一次 | 应用统一收集日志 |
| 子 logger 有 handler,propagate=True,root 也有 | 同一记录可能输出多次 | 一般应避免 |
| 子 logger 有 handler,propagate=False | 只在子 logger 的边界输出 | 模块需要独立文件或格式 |
因此,单纯把 root 的级别从 INFO 调到 WARNING,并不能可靠修复重复。祖先 logger 的 level/filter 不会替代 handler 去重;要修的是 handler 的归属边界。
怎么改才不会重复:只保留一个输出边界
集中式配置最简单:业务和库 logger 不直接添加 handler,统一交给 root 或某个应用顶层 logger。下面的示例把输出责任留给 root,子 logger 只负责产生记录。
import logging
root = logging.getLogger()
root.setLevel(logging.INFO)
# 演示时先清理旧 handler,避免重复运行配置函数叠加输出。
root.handlers.clear()
root.addHandler(logging.StreamHandler())
worker = logging.getLogger("app.worker")
worker.info("任务已进入队列") # 只由 root 的 handler 输出
如果某个库或模块必须写入独立文件,则反过来:只给它挂 FileHandler,并设置 propagate=False。这表示它的记录停在自己的输出边界,不再送到 root。生产代码还应让配置函数具备幂等性,避免每次初始化都再次 addHandler()。

一份不容易误判的检查清单
- 先打印目标 logger 和所有祖先的
handlers,确认是否挂了同类或不同类 handler。 - 再看传播链上第一个
propagate=False,它决定记录在哪里停止向上提供。 - 确认配置函数是否被重复调用,尤其检查是否每次调用都创建新的 StreamHandler。
- 最后用唯一消息和 handler 身份验证输出次数,不要用提高 level 的方式掩盖结构问题。
如果应用统一输出,推荐“子 logger 不挂 handler、root 或应用顶层只挂一次”;如果模块要独立输出,推荐“模块挂自己的 handler,同时 propagate=False”。两种方案都可以,但同一条记录不要同时拥有两个输出责任。
相关问题
把 propagate 设置为 False 会丢日志吗?
只有当当前 logger 及其子链没有可用 handler 时,才可能看不到输出。设置前要确认目标 logger 自己已经有负责写出的 handler。
为什么只调用 logging.basicConfig 仍然重复?
basicConfig() 只负责快速配置 root,而且 root 已有 handler 时默认不会重复配置。重复往往来自业务 logger 手动添加 handler,或初始化函数反复创建 handler。
能不能给父 logger 再加一个过滤器解决?
过滤器可以做业务筛选,但不是 handler 去重机制。先明确唯一输出边界,再决定是否需要过滤器。
-
346 收藏
-
235 收藏
-
387 收藏
-
447 收藏
-
360 收藏
-
152 收藏
-
274 收藏
-
278 收藏
-
470 收藏
-
文章 · python教程 | 7小时前 | 异常处理 · Python教程 · 兼容性 · ExceptionGroup · contextlib · ExceptionGroup Python contextlib.suppress BaseExceptionGroup except*431 收藏
-
文章 · python教程 | 8小时前 | 资源管理 · python · memoryview · 缓冲区协议 · 性能编程 · Python memoryview memoryview.release Python 缓冲区协议 bytearray BufferError mmap 资源释放275 收藏
-
311 收藏
-
415 收藏
-
154 收藏
-
文章 · python教程 | 14小时前 | 并发 · python · 线程安全 · http.server · Python http.server threading.Lock ThreadingHTTPServer344 收藏
-
453 收藏
-
212 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习