4. 实战:搭建第一个日志系统:使用 Python logging 模块配置控制台日志与文件日志,实现日志轮转
在生产环境中,日志是系统可靠性的基石。本节将带领你从零搭建一个符合工程标准的日志系统,重点掌握 logging 模块的核心组件配置,并实现基于时间的日志轮转策略,避免磁盘写满导致服务异常。
4.1 日志系统核心组件与架构
Python 的 logging 模块采用分层架构,理解其四大核心组件是配置的基础。下图展示了日志记录从产生到输出的完整链路。
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 日志轮转流程可视化
下图展示了基于时间的日志轮转机制在文件系统中的实际变化过程。
4.5 配置对比与选型建议
不同的日志输出场景需要选择不同的 Handler 组合。下表总结了常见配置的适用场景与优缺点。
| 配置方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 控制台 + 单文件 | 开发调试、小型脚本 | 配置简单,实时可见 | 文件无限增长,无轮转 |
| TimedRotatingFileHandler | 生产服务、定时任务 | 按时间自动切割,便于归档 | 高并发时轮转瞬间可能丢日志 |
| RotatingFileHandler | 日志量稳定的服务 | 按大小切割,控制单文件体积 | 无法按时间维度归档 |
| WatchedFileHandler | 配合 logrotate 系统工具 | 与系统日志管理工具兼容 | 依赖外部工具触发 |
工程建议: 对于微服务或容器化部署,推荐使用
TimedRotatingFileHandler + backupCount=30 作为本地日志兜底,同时将日志通过 stdout/stderr 输出到容器标准流,由容器编排平台(如 Kubernetes)统一采集。
4.6 避坑指南:常见配置错误
- 重复添加 Handler:多次调用
logger.addHandler()会导致日志重复输出。建议在模块初始化时只添加一次。 - Logger 名称冲突:使用
getLogger(__name__)按模块名创建,避免全局命名空间污染。 - 编码未指定:文件 Handler 默认使用系统编码,在 Windows 下可能导致乱码。始终显式指定
encoding="utf-8"。 - 轮转时间未对齐:
when="midnight"依赖系统时区,如果服务器时区配置错误,轮转时间会偏移。 - Handler 级别高于 Logger 级别:Logger 的级别是全局门槛,Handler 的级别是二次过滤。如果 Logger 设为 INFO,Handler 设为 DEBUG,DEBUG 日志仍不会输出。
验证方法: 配置完成后,手动修改系统时间触发轮转,检查归档文件是否按预期生成,并确认旧文件在超出
backupCount 后被正确清理。