当前位置:首页 > 文章列表 > 文章 > python教程 > Python logging propagate 为真时为何出现重复日志

Python logging propagate 为真时为何出现重复日志

来源:17golang原创 2026-09-15 06:39:14 0浏览 收藏

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.workerapp 的子 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 去执行输出逻辑,最终就会出现同一条日志被多次打印的重复现象。
Python logging 子 logger、父 logger、root logger 与 StreamHandler FileHandler 的传播关系示意图
图1:Python logging 层级与 handler 归属示意图,propagate=True 让同一记录可被子级和祖先级 handler 分别处理。

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()

Python logging 日志传播链路示意图
logging propagate 为 True 时的日志向上传递流程
Python logging 集中式输出与 propagate=False 独立输出边界示意图
图2:避免重复日志的配置边界示意图,集中式和独立式输出分别保留唯一的 handler 归属链。

一份不容易误判的检查清单

  1. 先打印目标 logger 和所有祖先的 handlers,确认是否挂了同类或不同类 handler。
  2. 再看传播链上第一个 propagate=False,它决定记录在哪里停止向上提供。
  3. 确认配置函数是否被重复调用,尤其检查是否每次调用都创建新的 StreamHandler。
  4. 最后用唯一消息和 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 去重机制。先明确唯一输出边界,再决定是否需要过滤器。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go []rune 转回字符串时为什么长度发生变化Go []rune 转回字符串时为什么长度发生变化
上一篇
Go []rune 转回字符串时为什么长度发生变化
Go reflect.TypeFor 如何在泛型函数中获取类型
下一篇
Go reflect.TypeFor 如何在泛型函数中获取类型
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    30次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    131次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    67次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    24次使用
  • CMMLU中文大模型评估基准:功能、使用教程与应用场景解析
    CMMLU
    深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
    13次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码