
本文详解如何在 Python 包中通过 logging.config.dictConfig 正确初始化日志配置,确保子模块能自动继承根 logger 设置,解决因配置位置错误、字段名不规范或格式器缺失导致的日志不输出问题。
本文详解如何在 python 包中通过 `logging.config.dictconfig` 正确初始化日志配置,确保子模块能自动继承根 logger 设置,解决因配置位置错误、字段名不规范或格式器缺失导致的日志不输出问题。
在构建可复用的工具包(如 core)时,统一日志配置是提升可观测性的关键一步。但若配置不当,即使子模块调用 logging.getLogger(__name__),日志也可能静默丢失——这通常并非代码逻辑错误,而是日志系统初始化时机与配置结构的问题。以下为经过验证的专业实践方案。
✅ 关键修正点
配置文件位置必须是 __init__.py,而非 __main__.py
__main__.py 仅在执行 python -m core 时触发,而包导入(如 from core.utils import test)会首先执行 core/__init__.py。将日志初始化逻辑移至此处,才能确保所有子模块在首次导入前完成 logger 树的构建。formatter 字段名必须为 "format",而非 "fmt"
logging.config.dictConfig 严格遵循 官方字典配置 Schema,"format" 是唯一识别的日志格式字符串键。使用 "fmt" 将导致 formatter 初始化失败,进而使 handler 无法格式化日志。-
日志格式字段需匹配所用 formatter 类型
- 若使用标准 logging.Formatter:应使用 %(levelname)s(如 "INFO"),%(levelprefix)s 是无效字段;
- 若依赖 uvicorn.logging.DefaultFormatter(支持彩色 INFO: 前缀),则需显式指定其类路径,并保留 %(levelprefix)s:
"formatters": { "default": { "()": "uvicorn.logging.DefaultFormatter", # 启用 Uvicorn 格式器 "format": "%(levelprefix)s %(asctime)s %(name)s:%(lineno)d: %(message)s", "datefmt": "%Y-%m-%d %H:%M:%S", } }
✅ 完整可运行配置示例
# core/__init__.py
import logging
import logging.config
from pydantic import BaseModel
class BaseLoggerConfig(BaseModel):
LOG_FORMAT: str = "%(levelname)s %(asctime)s %(name)s:%(lineno)d: %(message)s"
DATEFMT: str = "%Y-%m-%d %H:%M:%S"
LOG_LEVEL: int = logging.INFO
version: int = 1
disable_existing_loggers: bool = False
formatters: dict = {
"default": {
"format": LOG_FORMAT, # ⚠️ 必须是 'format',非 'fmt'
"datefmt": DATEFMT,
}
}
handlers: dict = {
"default": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr",
}
}
loggers: dict = {}
def __init__(self, name: str, **data):
super().__init__(**data)
# 为当前包名(如 'core')创建 logger 配置,并禁用传播以避免重复输出
self.loggers[name] = {
"handlers": ["default"],
"level": self.LOG_LEVEL,
"propagate": False,
}
# 初始化:在包导入时立即生效
LOG_CONFIG = BaseLoggerConfig("core") # 使用包名 'core',非 __name__
logging.config.dictConfig(LOG_CONFIG.dict())
# core/utils.py
import logging
logger = logging.getLogger(__name__) # → 'core.utils'
def test():
logger.info(f"I am a log from {__name__}")
# test.py
from core.utils import test
if __name__ == "__main__":
test() # 输出:INFO 2024-11-30 11:06:28 core.utils:8: I am a log from core.utils
⚠️ 注意事项与最佳实践
- propagate=False 的含义:在 core logger 上设置 propagate=False 可防止日志向上冒泡至 root logger(避免被未配置的 root handler 二次处理或丢弃),但需确保 core 及其子 logger 均被显式配置在 loggers 字典中;否则子模块(如 core.utils)将 fallback 到 root logger,而 root 默认 level 为 WARNING,导致 INFO 日志被忽略。
- 动态 logger 注册(进阶):若需支持任意子模块自动注册,可在 __init__.py 中监听 logging.getLogger 调用,或改用 logging.setLoggerClass() 自定义 logger 工厂。
- 测试验证技巧:在 test.py 中添加 print(logging.getLogger('core.utils').level) 和 print(logging.getLogger('core.utils').handlers),确认 logger 实例已正确继承配置。
通过以上调整,日志系统即可在包层级完成声明式初始化,所有 from core.xxx import yyy 的导入均能无缝获得一致、可控的日志能力——这才是 Python 包内日志继承的稳健实现方式。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











