Python logging如何同时输出日志到文件与stdout并采集跨模块日志
Python标准库logging的logger实例按名称分层继承,继承关系靠点分隔的名称前缀判定:比如名为a.b的logger是a的子logger,所有logger最终都继承自名称为空的根logger。默认配置下,日志会从子logger向上逐层传播,交给父logger绑定的handler输出。
你原有写法的核心问题是:主程序通过logging.getLogger(__name__)拿到的是名为__main__的独立logger,你只给这个logger绑定了文件、控制台输出handler;但其他业务模块里logging.getLogger(__name__)拿到的是对应模块名的logger(比如utils.db、service.user),这些logger和__main__ logger不存在继承关系,既没有单独绑定输出handler,也没法把日志传播到__main__的handler上,自然无法正常输出。
方案1:配置根logger(覆盖全模块,改造成本最低)
不需要修改其他业务模块的代码(其他模块保留logger = logging.getLogger(__name__)即可正常工作),只需要修改主文件的日志初始化逻辑,把handler绑定到根logger上:
import logging import sys # 获取根logger(名称为空字符串) logger = logging.getLogger() # 根logger设置最低放行级别,确保所有需要采集的日志都能进入handler logger.setLevel(logging.DEBUG) # 初始化文件输出handler fh = logging.FileHandler('logging.log', mode='a') fh.setLevel(logging.DEBUG) # 初始化控制台输出handler ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.INFO) # 设置日志格式 formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s') fh.setFormatter(formatter) ch.setFormatter(formatter) # 清理根logger默认自带的handler,避免日志重复输出 logger.handlers.clear() # 绑定handler到根logger logger.addHandler(fh) logger.addHandler(ch)
这种写法不仅能覆盖你自己写的所有业务模块的日志,还能统一采集项目依赖的第三方库输出的日志。
方案2:配置项目顶层包logger(适合需要隔离第三方日志的场景)
如果你的所有业务代码都放在同一个顶层包下(比如项目代码都在myapp目录下,模块名都是myapp.xxx格式),也可以不给根logger加配置,专门给顶层包名对应的logger绑定handler:
# 替换成你自己的顶层包名 logger = logging.getLogger("myapp") logger.setLevel(logging.DEBUG) # 后续handler绑定逻辑和方案1完全一致
这种写法只会采集你自己项目内模块的日志,第三方库的日志不会被输出,适合不需要看依赖库日志的场景。
- 不要在每个业务模块里重复给logger绑定handler,否则会出现同一条日志多次打印的问题
- 如果配置后发现日志重复输出,在绑定handler前先调用
logger.handlers.clear()清理掉logger上已存在的旧handler即可 - 日志级别校验是沿logger继承链从子到父逐层判断的,只要任意一层logger的级别高于待输出日志的级别,日志就会被直接丢弃,因此最顶层的公共logger必须设置为你需要采集的最低日志级别
- 非必要不要修改模块内logger的
propagate属性,该属性默认值为True(支持日志向上传播),如果设为False,上层logger将接收不到该模块的日志。
内容的提问来源于stack exchange,提问作者NineWasps

