Python logging中getLogger(__name__)最佳实践用法疑问
logging.getLogger(__name__)最佳实践的原理解答 你的测试场景存在核心认知偏差,才会觉得这个最佳实践不合理,先讲底层逻辑再纠正误区:
logging模块的核心运行逻辑
Python的logging模块是树形命名空间+继承传播的设计:
- 所有logger按名称中
.分隔形成父子层级,比如名为pkg.mod的logger是pkg的子节点,pkg是根logger的子节点 - 子logger会自动继承所有父节点的日志级别、handler、过滤器配置,不需要给每个logger单独绑定输出规则
- 不传参数调用
logging.getLogger()拿到的是根logger,是所有logger的最终祖先 - 只要给任意父节点绑定handler,所有子节点的日志在满足级别要求时,都会向上传播到父节点的handler输出,不需要子节点自己配输出
为什么getLogger(__name__)是公认最佳实践
这个写法完全适配logging的设计,没有任何多余逻辑:
- 零硬编码成本:
__name__是Python给每个模块自动注入的内置变量,值是模块的导入全路径,模块改名、调整包结构时,logger名会自动同步,不会出现硬编码名称写错、重构后失效的问题 - 天然匹配层级结构:每个模块用自己的
__name__拿logger,自动归属于对应包的命名空间下,根节点统一配一次日志规则,全项目所有模块的日志都能正常输出 - 溯源精准:日志格式里的
%(name)s字段会直接打印日志来源的模块全路径,排查问题时能直接定位到产生日志的文件,不需要自己给每个模块想名字
你测试代码的问题
你遇到的日志不输出问题,和最佳实践本身无关,是三个写法错误导致的:
- 日志配置绑错了logger:你把handler绑在了入口脚本对应的
__main__logger上,而不是根logger上。当你直接运行main_module.py时,这个模块的__name__是特殊值__main__,它和其他包下的模块(比如你例子里的cookbook_example.auxiliary_module)是平级关系,没有父子继承关系,其他模块的logger自然拿不到你配在__main__上的输出规则。 - 对logger返回值的认知错误:你认为辅助模块直接调用
getLogger(__name__)会返回根logger,这个判断是错的——只要传入非空名称,getLogger永远返回对应名称的logger,不会返回根logger。你看不到日志是因为这个独立logger没有绑定任何handler,也没有继承到任何父节点的配置,默认只接受WARNING及以上级别的日志,INFO级别的日志直接被丢弃了。 - 你为了凑继承链写的
getLogger(f'__main__.{__name__}')是典型反模式:只要换个入口脚本导入这个辅助模块,拼接出来的logger名就会和新入口的__main__断开关联,日志直接失效,完全没有可移植性,也会导致日志里的logger名混乱,失去溯源价值。
正确的规范写法
只需要调整入口处的日志配置位置,所有模块统一用getLogger(__name__)即可,不需要任何前缀拼接:
# main_module.py 入口脚本 import logging from cookbook_example import auxiliary_module # 第一步:给根logger配置全局日志规则,不要传__name__ root_logger = logging.getLogger() root_logger.setLevel(logging.DEBUG) fh = logging.FileHandler('spam.log', mode='w') fh.setLevel(logging.DEBUG) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh.setFormatter(formatter) root_logger.addHandler(fh) # 入口模块自己的logger,正常用__name__获取 main_logger = logging.getLogger(__name__) main_logger.info('Creating instance of auxiliary_module.Auxiliary') a = auxiliary_module.Auxiliary() main_logger.info('Calling auxiliary_module.do_something') a.do_something() auxiliary_module.some_function()
# cookbook_example/auxiliary_module.py 被导入的模块 import logging # 不需要任何特殊拼接,直接用__name__ module_logger = logging.getLogger(__name__) def some_function(): module_logger.info('received a call to "some_function"')
改完之后运行,所有日志都会正常输出到文件,辅助模块的日志name会显示为cookbook_example.auxiliary_module,入口模块的日志name显示为__main__,名称准确,也不需要任何硬编码。
和独立日志配置的适配
你提到的“日志配置放在独立配置文件/配置字典”的工程化场景,这个写法反而更适配:
- 加载配置时只需要给根logger配置默认输出规则,所有模块的logger自动继承配置,不需要修改任何业务模块的代码
- 如果需要给特定包/模块单独调整日志级别、加专属输出(比如屏蔽第三方库的冗余日志、给核心业务模块单独存一份日志),直接按
__name__对应的命名空间配置规则即可,比如给cookbook_example命名空间单独绑定一个handler,所有该包下子模块的日志都会自动应用规则,灵活度极高。
参考实现
几乎所有生产级Python项目都沿用这个写法,Python标准库内部、Requests、Flask、Django等主流项目的源码中,每个模块都是用getLogger(__name__)初始化模块级logger,日志统一在入口层配置,模块内部不单独绑定handler。
内容的提问来源于stack exchange,提问作者meg hidey
相关产品推荐
相关产品推荐

