3. Python 日志模块入门:logging 核心概念
在结构化日志实践中,Python 内置的 logging 模块是基础设施。它提供了四个核心组件:Logger、Handler、Formatter 和 Filter。理解它们之间的关系与职责,是构建可观测、可维护日志系统的第一步。
3.1 四组件职责与协作关系
下图展示了日志记录从产生到输出的完整链路:Logger 接收日志调用,经过 Filter 筛选后,将 LogRecord 传递给 Handler,Handler 使用 Formatter 格式化后输出到目标(控制台、文件等)。
3.2 组件职责详解
| 组件 | 职责 | 典型场景 |
|---|---|---|
| Logger | 日志记录入口,提供 debug()、info()、warning()、error()、critical() 等方法。支持层级命名(如 app.service.module)。 |
每个模块或类创建一个 Logger 实例,通过名称区分日志来源。 |
| Handler | 决定日志的输出目的地。常见实现包括 StreamHandler(控制台)、FileHandler(文件)、RotatingFileHandler(轮转文件)。 |
开发环境输出到控制台,生产环境同时输出到文件和日志收集系统。 |
| Formatter | 定义日志记录的最终文本格式。通过 %(name)s、%(levelname)s、%(message)s 等占位符组合。 |
结构化日志中常使用 JSON 格式的 Formatter,便于后续解析。 |
| Filter | 提供更细粒度的日志过滤能力。可以基于日志级别、Logger 名称、自定义属性等条件决定是否放行某条日志。 | 只允许特定模块的 DEBUG 日志通过,或过滤掉包含敏感信息的日志。 |
3.3 快速入门示例
以下代码展示了 logging 模块最基础的配置与使用方式:
import logging
# 1. 创建 Logger
logger = logging.getLogger("my_app")
logger.setLevel(logging.DEBUG)
# 2. 创建 Handler(控制台输出)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
# 3. 创建 Formatter 并绑定到 Handler
formatter = logging.Formatter(
fmt="%(asctime)s | %(name)s | %(levelname)-8s | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
console_handler.setFormatter(formatter)
# 4. 将 Handler 添加到 Logger
logger.addHandler(console_handler)
# 5. 使用 Logger 记录日志
logger.debug("这是一条 DEBUG 日志")
logger.info("这是一条 INFO 日志")
logger.warning("这是一条 WARNING 日志")
logger.error("这是一条 ERROR 日志")
实践建议:在生产代码中,不要直接使用
logging.basicConfig() 进行全局配置。推荐为每个模块创建独立的 Logger 实例,并通过配置文件或代码显式管理 Handler 和 Formatter,以便在测试和 Mock 场景中灵活替换。
3.4 日志级别与传播机制
Logger 默认继承父 Logger 的 Handler,形成层级传播。理解级别和传播规则,能避免日志重复或丢失。
| 级别 | 数值 | 使用场景 |
|---|---|---|
DEBUG |
10 | 调试信息,仅在开发环境开启 |
INFO |
20 | 正常操作的关键节点信息 |
WARNING |
30 | 潜在问题,不影响当前功能 |
ERROR |
40 | 功能异常,需要关注 |
CRITICAL |
50 | 严重错误,可能导致系统崩溃 |
常见陷阱:Logger 的
setLevel() 和 Handler 的 setLevel() 是独立生效的。Logger 级别决定是否生成 LogRecord,Handler 级别决定是否输出该记录。两者取“较高”者(数值较大)为实际生效级别。例如 Logger 设为 DEBUG,Handler 设为 WARNING,则只有 WARNING 及以上级别的日志才会输出。
3.5 层级 Logger 与传播示例
通过层级命名,可以实现精细的日志控制。下面的 SVG 展示了父子 Logger 的传播关系:
3.6 最佳实践要点
- 按模块命名 Logger:使用
__name__作为 Logger 名称,自动继承包层级结构。 - 避免重复日志:检查
logger.handlers是否为空,再添加 Handler,防止重复绑定。 - 结构化先行:从项目初期就使用
extra参数传递结构化字段,为后续 JSON 格式化做准备。 - 测试友好:在单元测试中,使用
unittest.mock.patch或logging.Handler的子类捕获日志,验证日志内容。
核心记忆点:Logger 是入口,Handler 是出口,Formatter 是翻译,Filter 是守门员。四者组合,构成了 Python 日志体系的完整骨架。