当前位置:首页 > 文章列表 > 文章 > python教程 > Python logging 为什么重复输出:propagate、handler 和层级配置的排查方法

Python logging 为什么重复输出:propagate、handler 和层级配置的排查方法

来源:17golang原创 2026-08-25 01:21:39 0浏览 收藏

开发环境里最烦人的日志问题之一,是同一条 INFO 在终端出现两遍,甚至一条带时间、一条不带时间。很多人先去搜业务代码,怀疑某个函数被调用了两次;但在 Python logging 里,更常见的原因是子 logger 把记录交给自己的 handler 后,又沿层级传给了父 logger。

你开发时大概率碰到过这类场景:代码里明明只写了一次日志打印,终端却连续输出好几条完全相同的日志,排查半天业务逻辑也没找到重复调用,问题其实出在logging模块的层级传播和handler挂载规则上。

排查这类重复输出问题,优先按两个方向验证:第一检查当前日志记录是否同时被子logger和父/根logger的handler捕获,第二检查日志配置初始化函数有没有被多次执行重复添加handler。
要点速览
  • 先看同一条记录是否同时命中了子 logger 和根 logger 的 handler。
  • 子 logger 已经有专用 handler 时,通常把 propagate 设为 False。
  • 全局配置必须只初始化一次,重复添加 StreamHandler 也会造成成倍输出。
  • 修复后要同时核对 logger 的 handlers、父级关系和实际输出行数。

先用一段最小代码复现重复日志

下面的代码故意把同一个消息交给两个层级。app.db 有自己的终端处理器,根 logger 也有一个处理器;默认情况下,子 logger 处理完还会继续向上冒泡。

import logging

root = logging.getLogger()
root.setLevel(logging.INFO)
root.addHandler(logging.StreamHandler())

db_logger = logging.getLogger("app.db")
db_logger.setLevel(logging.INFO)
db_logger.addHandler(logging.StreamHandler())

db_logger.info("连接池已准备")

你会看到两行相同的“连接池已准备”。这不代表 info() 被执行了两次,而是同一条 LogRecord 先被 app.db 的 handler 输出,再因为 propagate=True 交给根 logger 的 handler 输出。

Python logging app.db 子 logger 先命中 handler 又沿 propagate 传到根 logger的重复输出链路

重复输出的关键:logger、handler 和 propagate 各管什么

排查时不要把三个概念混成一个。logger 负责接收日志记录并决定级别,handler 负责把记录写到终端或文件,propagate 只决定记录是否继续交给父 logger 的 handler。

logger 名称决定层级关系

logging.getLogger("app.db") 的父级不是一个手动保存的对象,而是名称层级中的 app,再向上才是根 logger。只要没有显式设置,记录就可能一路传到根 logger。

handler 才是真正写出一行文字的地方

同一个 logger 上挂两个 StreamHandler,也会出现两行;父子 logger 各挂一个,也会出现两行。判断重复来源,最有效的是直接打印处理器数量,而不是猜业务调用链。

logger = logging.getLogger("app.db")
print("name=", logger.name)
print("handlers=", logger.handlers)
print("propagate=", logger.propagate)
print("parent=", logger.parent.name or "root")

三步排查:先数 handler,再看传播,最后查初始化

第一步:确认是谁挂了 handler

如果项目使用了多个模块,先把目标 logger 和根 logger 的处理器列出来。logger.handlers 只包含当前 logger 的处理器,不包含父级;这正好能帮助你区分“自己重复挂载”和“向上冒泡”两类问题。

def show_logging_path(name: str) -> None:
    logger = logging.getLogger(name)
    current = logger
    while current is not None:
        print(
            current.name or "root",
            "handlers=", len(current.handlers),
            "propagate=", getattr(current, "propagate", False),
        )
        current = current.parent

show_logging_path("app.db")

第二步:明确只保留一条输出路径

如果应用希望所有日志都由根 logger 统一格式化,子 logger 不要再添加输出 handler,只设置级别,然后保持 propagate=True。如果数据库模块必须写到独立文件,则保留子 logger 的 handler,并设置 propagate=False,避免它再回到根 logger。

第三步:检查配置函数是否重复进入

模块被重复导入通常不会重复执行同一个模块体,但配置函数可能在Web启动、测试夹具和热重载入口分别调用。最稳妥的做法是让配置函数具备幂等性:配置前先清理自己管理的 handler,或用明确的初始化标记。

def configure_db_logger() -> logging.Logger:
    logger = logging.getLogger("app.db")
    logger.setLevel(logging.INFO)
    logger.propagate = False

    if not logger.handlers:
        handler = logging.StreamHandler()
        handler.setFormatter(logging.Formatter("DB %(levelname)s %(message)s"))
        logger.addHandler(handler)
    return logger

这里的 if not logger.handlers 只适用于这个 logger 的 handler 确实由当前配置函数独占的场景。如果测试需要重新配置输出目标,应该显式移除旧 handler,而不是无条件追加新 handler。

Python logging 将 app.db 的日志固定到单一 handler 并关闭 propagate 后的修复路径

两种常见架构,选对 handler 归属

目标子 loggerpropagate结果
统一终端格式不挂输出 handlerTrue由根 logger 集中输出
模块单独写文件挂文件 handlerFalse只走模块自己的输出
临时调试临时 handler按需调试结束后移除 handler

几个容易误判的坑

把 level 当成去重开关

把子 logger 的级别调成 WARNING 只能过滤低级别记录,不能阻止已经通过过滤的记录继续冒泡。去重首先看 handler 归属和 propagate。

只看 logger.handlers,漏掉根 logger

子 logger 的处理器列表为空,不代表没有输出路径。它可能完全依赖父级;排查必须沿 parent 逐级检查,尤其是根 logger。

测试环境反复配置但没有清理

如果每个测试都向同一个 logger 追加一个处理器,单测越跑输出的日志行数越多。测试结束时移除本次创建的 handler,并关闭它;不要用全局清理破坏其他测试的日志设置。

修复后的验收方式

修复不是把终端里的两行看成一行就结束。给日志内容加一个唯一标识,在一次调用中记录它,然后核对:目标 logger 的 handler 数量、父级是否仍然存在输出 handler、propagate 值,以及捕获到的行数是否为 1。

logger = configure_db_logger()
logger.info("probe-logging-001")

# 预期:终端只出现一行 probe-logging-001
# 若仍出现两行,继续沿 logger.parent 检查 handler

相关问题

为什么 basicConfig 有时不生效?

默认情况下,根 logger 已经有 handler 时,basicConfig() 不会再次替换配置。调试启动顺序时,先看根 logger 当前是否已有处理器,再决定是否在明确场景下使用强制重配置。

关闭 propagate 会不会让日志丢失?

如果子 logger 自己没有可用 handler,关闭传播会让记录没有输出。关闭前必须确认子 logger 已经挂好目标 handler,并且级别和格式都符合预期。

文件 handler 和终端 handler 可以同时保留吗?

可以,但要明确这是“同一条记录写两个目的地”,不是重复故障。若只想终端或文件二选一,就让记录只经过对应的输出路径。

把日志配置收口,重复问题就不再靠猜

Python logging 的重复输出大多能归结为两件事:同一层级挂了多个 handler,或者子 logger 输出后又通过 propagate 传给了父级。把配置入口收口,明确 handler 的归属,再用唯一标识核对实际行数,通常比在业务函数里到处加打印更快。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Java StringJoiner 怎么拼带前后缀的列表:空集合、嵌套分隔和流式收集Java StringJoiner 怎么拼带前后缀的列表:空集合、嵌套分隔和流式收集
上一篇
Java StringJoiner 怎么拼带前后缀的列表:空集合、嵌套分隔和流式收集
Linux 服务启动慢怎么定位:After、Requires 与启动时间线的核对方法
下一篇
Linux 服务启动慢怎么定位:After、Requires 与启动时间线的核对方法
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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模型性能。
    390次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    470次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    477次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    421次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    245次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码