4. 实战:搭建第一个日志系统:使用 Python logging 模块配置控制台日志与文件日志,实现日志轮转

在生产环境中,日志是系统可靠性的基石。本节将带领你从零搭建一个符合工程标准的日志系统,重点掌握 logging 模块的核心组件配置,并实现基于时间的日志轮转策略,避免磁盘写满导致服务异常。

4.1 日志系统核心组件与架构

Python 的 logging 模块采用分层架构,理解其四大核心组件是配置的基础。下图展示了日志记录从产生到输出的完整链路。

Python logging 核心组件架构 Logger 记录器(应用程序接口) Handler 处理器(输出目的地) Formatter 格式化器(输出格式) Filter 过滤器(日志级别/内容) 工作流程: 应用程序 → Logger(记录器) → Filter(过滤器) → Handler(处理器) → Formatter(格式化器) → 输出

4.2 基础配置:控制台日志 + 文件日志

以下实战代码演示如何同时向控制台和文件输出日志,并设置不同的日志级别与格式。这是生产环境中最常见的双通道配置模式。

实战要点: 控制台用于实时调试(级别 DEBUG),文件用于持久化记录(级别 INFO),两者互不干扰。
import logging
import logging.handlers
import sys

# 1. 创建 Logger 实例
logger = logging.getLogger("AppLogger")
logger.setLevel(logging.DEBUG)  # 全局最低级别

# 2. 创建控制台 Handler(输出到 stderr)
console_handler = logging.StreamHandler(sys.stderr)
console_handler.setLevel(logging.DEBUG)

# 3. 创建文件 Handler(输出到 app.log)
file_handler = logging.FileHandler("app.log", encoding="utf-8")
file_handler.setLevel(logging.INFO)

# 4. 定义 Formatter
console_format = logging.Formatter(
    "[%(asctime)s] %(levelname)-8s %(message)s",
    datefmt="%H:%M:%S"
)
file_format = logging.Formatter(
    "[%(asctime)s] [%(name)s] %(levelname)-8s %(filename)s:%(lineno)d - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)

# 5. 绑定 Formatter 到 Handler
console_handler.setFormatter(console_format)
file_handler.setFormatter(file_format)

# 6. 将 Handler 添加到 Logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)

# 7. 测试日志输出
logger.debug("这是一条 DEBUG 日志(仅控制台可见)")
logger.info("这是一条 INFO 日志(控制台 + 文件均可见)")
logger.warning("这是一条 WARNING 日志")
logger.error("这是一条 ERROR 日志")

4.3 日志轮转:基于时间的自动切割

单一日志文件会无限增长,最终耗尽磁盘空间。使用 TimedRotatingFileHandler 可以按时间周期自动切割和归档旧日志。

参数 说明 常用值
when 轮转时间单位 'S'(秒)、'M'(分钟)、'H'(小时)、'D'(天)、'W0'-'W6'(星期几)
interval 轮转间隔数量 1(每1个时间单位)、7(每7天)
backupCount 保留的备份文件数 7(保留最近7个备份)、30(保留30天)
encoding 日志文件编码 'utf-8'
磁盘容量预警: 即使配置了轮转,如果 backupCount 设置过大或日志写入量极高,仍可能撑满磁盘。建议结合 RotatingFileHandler(按文件大小轮转)或外部日志采集工具(如 Filebeat)进行双重保障。
import logging
import logging.handlers
import time

# 创建按天轮转的文件 Handler
rotating_handler = logging.handlers.TimedRotatingFileHandler(
    filename="app_rotating.log",
    when="midnight",       # 每天午夜轮转
    interval=1,
    backupCount=7,         # 保留最近7天的日志
    encoding="utf-8",
    utc=False
)

# 设置后缀格式(自动追加到文件名)
rotating_handler.suffix = "%Y-%m-%d"
# 例如:app_rotating.log → app_rotating.log.2025-04-08

rotating_handler.setLevel(logging.INFO)
rotating_handler.setFormatter(logging.Formatter(
    "[%(asctime)s] %(levelname)-8s %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
))

# 添加到 Logger
logger = logging.getLogger("RotatingApp")
logger.setLevel(logging.DEBUG)
logger.addHandler(rotating_handler)

# 模拟连续日志写入
for i in range(5):
    logger.info(f"业务日志记录 #{i+1}")
    time.sleep(1)

4.4 日志轮转流程可视化

下图展示了基于时间的日志轮转机制在文件系统中的实际变化过程。

TimedRotatingFileHandler 轮转流程 app_rotating.log 当前活跃日志 午夜 00:00 触发 重命名 + 新建 app_rotating.log.2025-04-08 归档备份(保留7份) app_rotating.log.2025-04-01 超出 backupCount=7 被删除 关键行为: 轮转时不会丢失日志 → 旧文件重命名 → 新建空文件继续写入 → 超出备份数的旧文件自动删除

4.5 配置对比与选型建议

不同的日志输出场景需要选择不同的 Handler 组合。下表总结了常见配置的适用场景与优缺点。

配置方案 适用场景 优点 缺点
控制台 + 单文件 开发调试、小型脚本 配置简单,实时可见 文件无限增长,无轮转
TimedRotatingFileHandler 生产服务、定时任务 按时间自动切割,便于归档 高并发时轮转瞬间可能丢日志
RotatingFileHandler 日志量稳定的服务 按大小切割,控制单文件体积 无法按时间维度归档
WatchedFileHandler 配合 logrotate 系统工具 与系统日志管理工具兼容 依赖外部工具触发
工程建议: 对于微服务或容器化部署,推荐使用 TimedRotatingFileHandler + backupCount=30 作为本地日志兜底,同时将日志通过 stdout/stderr 输出到容器标准流,由容器编排平台(如 Kubernetes)统一采集。

4.6 避坑指南:常见配置错误

  1. 重复添加 Handler:多次调用 logger.addHandler() 会导致日志重复输出。建议在模块初始化时只添加一次。
  2. Logger 名称冲突:使用 getLogger(__name__) 按模块名创建,避免全局命名空间污染。
  3. 编码未指定:文件 Handler 默认使用系统编码,在 Windows 下可能导致乱码。始终显式指定 encoding="utf-8"。
  4. 轮转时间未对齐:when="midnight" 依赖系统时区,如果服务器时区配置错误,轮转时间会偏移。
  5. Handler 级别高于 Logger 级别:Logger 的级别是全局门槛,Handler 的级别是二次过滤。如果 Logger 设为 INFO,Handler 设为 DEBUG,DEBUG 日志仍不会输出。
验证方法: 配置完成后,手动修改系统时间触发轮转,检查归档文件是否按预期生成,并确认旧文件在超出 backupCount 后被正确清理。