
本文详解如何在 python 包中通过 logging.config.dictconfig 正确初始化日志配置,解决子模块日志不生效问题,涵盖配置文件位置、格式键名修正、字段有效性验证及 uvicorn 兼容方案。
本文详解如何在 python 包中通过 logging.config.dictconfig 正确初始化日志配置,解决子模块日志不生效问题,涵盖配置文件位置、格式键名修正、字段有效性验证及 uvicorn 兼容方案。
在构建可复用的核心工具包(如 core)时,常需统一配置日志行为——但若配置不当,下游模块调用 logging.getLogger(__name__) 后却无输出,极易令人困惑。根本原因往往不在日志调用本身,而在于配置时机、作用域与字典结构的合规性。以下为经过验证的完整解决方案。
✅ 关键修正点
配置入口必须是 core/__init__.py,而非 __main__.py
__main__.py 仅在 python -m core 时执行,而包导入(如 import core.utils)会触发 __init__.py。日志配置需在包首次加载时完成,否则后续 getLogger() 获取的是未配置的默认 Logger 实例。Formatter 字段名必须为 "format",而非 "fmt"
logging.config.dictConfig() 严格遵循官方字典 Schema,"format" 是唯一合法键名;使用 "fmt" 将导致格式器静默失效。日志格式字符串中避免非法字段
%(levelprefix)s 并非标准 logging 字段,它是 Uvicorn 自定义 Formatter 的扩展属性。若未显式指定该 Formatter 类,则应改用标准字段 %(levelname)s(输出 INFO/DEBUG 等纯文本)。
✅ 正确配置示例(core/__init__.py)
# 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"
"datefmt": DATEFMT,
}
}
filters: dict = {}
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") # 显式指定包名,避免 __name__ 在 __init__.py 中为 "__main__"
logging.config.dictConfig(LOG_CONFIG.dict())
? 注意:BaseLoggerConfig("core") 中硬编码 "core" 更可靠——__name__ 在 __init__.py 中可能为 "__main__"(取决于执行方式),导致日志器注册失败。
✅ 子模块日志使用(core/utils.py)
# core/utils.py
import logging
logger = logging.getLogger(__name__) # → "core.utils"
def test():
logger.info(f"I am a log from {__name__}")
✅ 应用层调用(test.py)
# test.py from core.utils import test test() # 输出:INFO 2024-11-30 11:06:28 core.utils:8: I am a log from core.utils
? 进阶:兼容 Uvicorn 的彩色 levelprefix
若需保留 %(levelprefix)s(如绿色 INFO:),需显式启用 Uvicorn 的 Formatter:
# core/__init__.py 中修改 formatters 部分:
formatters: dict = {
"default": {
"()": "uvicorn.logging.DefaultFormatter", # ✅ 启用自定义 Formatter
"format": LOG_FORMAT, # 仍需保持 "format" 键名
"datefmt": DATEFMT,
}
}
此时 LOG_FORMAT 可安全使用 %(levelprefix)s,输出形如:
INFO: 2024-12-01 14:13:14 core.utils:6: I am a log from core.utils
⚠️ 重要注意事项
- 配置仅执行一次:dictConfig() 应在包初始化早期调用,重复调用可能导致 Handler 冲突或日志丢失。
- propagate=False 的意义:阻止日志向上递归到根 Logger,避免重复输出;若需全局捕获,可设为 True 并配置根 Logger。
- 避免在模块级创建 Logger 实例过早:确保 core/__init__.py 已完成配置,再导入其他模块(推荐将 core 作为顶层依赖优先导入)。
通过以上调整,即可实现日志配置的“一次定义、处处生效”,为内部工具包构建健壮、可维护的日志基础设施。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











