logging.getlogger(__name__)是必须的,因为它基于模块名自动生成带包路径的logger(如a.b.c),实现按模块隔离、层级继承和独立配置,避免所有日志挤入root logger导致混乱。

为什么 logging.getLogger(__name__) 是必须的
Python 多模块项目里,日志乱成一锅粥的根源,往往就出在 logger 实例没按模块隔离。直接用 logging.getLogger() 拿到的是 root logger,所有模块写日志全挤进去,根本分不清谁打的、谁该关、谁要调级别。
- 每个模块顶部统一写
logger = logging.getLogger(<strong>name</strong>),利用<strong>name</strong>自动生成带包路径的 logger 名(比如a.b.c),天然支持层级继承和独立配置 - 不要手写字符串名,比如
logging.getLogger("myapp"),否则跨模块时容易重名或漏配,也断了父子关系 - 如果用了
if <strong>name</strong> == "<strong>main</strong>"启动脚本,它的<strong>name</strong>是"<strong>main</strong>",不是包名,此时建议显式传入稳定名:logging.getLogger("myapp.main")
Handler 冲突导致日志重复打印
跑两次脚本,日志就翻倍;加个测试,控制台突然冒出四条一样的 INFO —— 这不是代码逻辑问题,是 Handler 被反复添加了。
- 每次调用
logger.addHandler()都会新增一个 handler,而 logger 实例在模块级常驻,import 一次、多次执行就会叠加 - 解决方法只有一条:所有 handler 添加逻辑必须包裹在
if not logger.handlers:判断里 - 更稳妥的做法是:只在入口文件(如
main.py)配置 root logger 和 handlers,其他模块只用getLogger(<strong>name</strong>)获取,不碰 addHandler - 注意:
basicConfig()也受此影响,它只在 root logger 没 handler 时生效,但一旦你手动加过 handler,再调basicConfig()就完全失效
level 设置错位:为什么 DEBUG 日志死活不出来
明明代码里写了 logger.debug("xxx"),却什么也不输出——大概率是三个 level 没对齐:logger 自身 level、handler level、root logger level。
- logger 的 level 控制“是否把这条日志传给 handler”,handler 的 level 控制“是否真的输出它”,两者是 AND 关系
- 常见错误:只设了
handler.setLevel(logging.DEBUG),但忘了logger.setLevel(logging.DEBUG),结果日志在第一道门就被拦下 - 另一个坑:调用
logging.basicConfig(level=logging.INFO)会自动给 root logger 设 level,并附加一个 StreamHandler;如果你后续又 get 了一个子 logger(如logging.getLogger("a.b")),它默认继承 root 的 level,但若没显式 setLevel,就可能被 root 的 INFO 挡住 DEBUG - 简单验证法:打印
logger.level和[h.level for h in logger.handlers],两处都得 ≤ DEBUG 才行
格式化器(Formatter)里别硬编码时间或进程信息
有人喜欢在 Formatter 的 fmt 字符串里写死 "[2024-01-01] %(levelname)s %(message)s",或者手动拼 os.getpid() —— 这会导致时间不准、多进程 ID 错乱、无法动态开关字段。
立即学习“Python免费学习笔记(深入)”;
- 所有动态内容必须交给 logging 的内置占位符:用
%(asctime)s而不是datetime.now().isoformat(),用%(process)d而不是os.getpid() -
%(asctime)s默认精度只到毫秒,如果需要微秒,得初始化 Formatter 时传datefmt并启用logging.Formatter.converter = time.time(注意:microsecond 需自己扩展) - 如果要用结构化日志(比如 JSON),不要用
fmt拼字符串,改用自定义Formatter.format()方法,在里面构造 dict 再 json.dumps —— 否则字段顺序、转义、空值都会出问题
日志规范最难的不是写对那几行配置,而是让所有人(包括半年后的你自己)在新增模块时,下意识敲出 getLogger(<strong>name</strong>),而不是随手 new 一个。这点一旦松动,整套体系就从内部开始瓦解。










