Python多文件项目基于继承的日志配置问题
问题场景还原
你在Python 3.6的多模块项目里配置统一日志时遇到了困惑:
当你用显式指定日志器名称的方式时,代码能正常输出日志:
main.py代码:
import logging logger = logging.getLogger('a') logger.setLevel(logging.DEBUG) ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) logger.addHandler(ch) import GUI GUI.hello()
GUI/__init__.py代码:
import logging module_logger = logging.getLogger('a.GUI') module_logger.info(__name__ + ' imported') def hello(): module_logger.info('hello')
能得到预期输出:
GUI imported hello
但当你把两处的日志器名称都换成__name__(即logging.getLogger(__name__))时,却没有任何输出。你希望用__name__来避免后续重构,同时想要更细粒度的日志配置方案。
问题根源分析
核心原因是主模块的__name__特殊性:
- 当你直接运行
main.py时,它的__name__是__main__,而不是你以为的main或者项目根名称。 - 此时
main.py里的日志器是logging.getLogger('__main__'),而GUI/__init__.py里的日志器是logging.getLogger('GUI')——这两个日志器属于完全独立的分支,不存在父子继承关系,所以GUI模块的日志器不会继承__main__日志器的级别和Handler配置。 - 而之前显式用
'a'和'a.GUI'时,后者是前者的子日志器,会自动继承父日志器的级别、Handler等属性,所以能正常输出。
解决方案与优化方案
1. 快速修复:统一项目根日志器
你可以给所有模块的日志器指定一个统一的项目根前缀,比如myproject,这样即使主模块是__main__,也可以手动关联到根日志器:
修改main.py:
import logging # 手动指定项目根日志器,而不是用__name__ root_logger = logging.getLogger('myproject') root_logger.setLevel(logging.DEBUG) ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) root_logger.addHandler(ch) # 如果需要,也可以给主模块的日志器关联到根日志器 main_logger = logging.getLogger('myproject.main') import GUI GUI.hello()
修改GUI/__init__.py:
import logging # 用项目根前缀 + __name__,保证是根日志器的子日志器 module_logger = logging.getLogger('myproject.' + __name__) module_logger.info(__name__ + ' imported') def hello(): module_logger.info('hello')
这样所有模块的日志器都是myproject的子日志器,会自动继承根日志器的配置,同时用__name__避免了硬编码模块名。
2. 更具扩展性的日志配置方案
如果想要更灵活的细粒度配置(比如不同模块用不同级别、不同输出目标),推荐下面两种方案:
方案A:用配置文件统一管理
可以用logging.config.dictConfig来加载配置,把所有日志规则写在配置文件里,代码里只需要初始化一次即可。
比如创建一个logging_config.py配置文件:
LOGGING_CONFIG = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s' }, }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'formatter': 'standard', 'level': 'DEBUG', }, 'file': { 'class': 'logging.FileHandler', 'formatter': 'standard', 'level': 'INFO', 'filename': 'app.log', }, }, 'loggers': { 'myproject': { # 项目根日志器 'handlers': ['console', 'file'], 'level': 'DEBUG', 'propagate': True }, 'myproject.GUI': { # 给GUI模块单独配置级别 'handlers': ['console'], 'level': 'INFO', 'propagate': False } } }
然后在main.py里初始化:
import logging import logging.config from logging_config import LOGGING_CONFIG logging.config.dictConfig(LOGGING_CONFIG) import GUI GUI.hello()
其他模块依然用logging.getLogger('myproject.' + __name__),这样可以轻松给不同模块配置不同的日志规则。
方案B:封装日志初始化模块
创建一个utils/log_setup.py模块,封装日志初始化逻辑:
import logging def setup_logging(root_name='myproject', level=logging.DEBUG): root_logger = logging.getLogger(root_name) root_logger.setLevel(level) # 避免重复添加Handler(如果多次调用初始化) if not root_logger.handlers: ch = logging.StreamHandler() ch.setLevel(level) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') ch.setFormatter(formatter) root_logger.addHandler(ch)
然后在main.py里调用:
from utils.log_setup import setup_logging setup_logging() import logging main_logger = logging.getLogger('myproject.main') import GUI GUI.hello()
GUI模块还是用logging.getLogger('myproject.' + __name__),这样所有配置都集中在log_setup.py里,后续修改只需要改这一个文件即可。
内容的提问来源于stack exchange,提问作者ini

