Python 3.12 logging Handler – RotatingFileHandler 详解
RotatingFileHandler 是 Python logging 模块中用于实现日志文件大小轮转的处理器。当日志文件达到指定大小时,它会自动关闭当前文件,重命名并备份,然后创建新的日志文件继续写入。这种机制可以有效防止单个日志文件无限增长,同时保留历史日志记录。
一、RotatingFileHandler 核心概念
1.1 工作原理
- 程序持续向当前日志文件(如 app.log)写入日志。
- 当文件大小达到 maxBytes 阈值时,触发轮转操作。
- 将当前文件重命名为 app.log.1,原有的 app.log.1 变为 app.log.2,以此类推。
- 创建新的空文件 app.log 继续写入。
- 最多保留 backupCount 个备份文件,超出数量的最旧文件被删除。
1.2 适用场景
- 长时间运行的服务(Web 应用、后台任务)
- 日志量较大的系统
- 需要保留历史日志但磁盘空间有限的环境
二、RotatingFileHandler 完整配置项解析
| filename | str | 必需 | 日志文件路径(可绝对或相对) |
| mode | str | 'a' | 文件打开模式,必须使用 'a'(追加),不能使用 'w',否则轮转时会清空文件内容 |
| maxBytes | int | 0 | 单个日志文件最大字节数。设为 0 表示不轮转 |
| backupCount | int | 0 | 保留的备份文件数量。如果设为 5,则备份文件为 .1 .2 … .5 |
| encoding | str | None | 文件编码,如 'utf8' |
| delay | bool | False | 是否延迟打开文件直到第一条日志写入 |
| errors | str | None | 编码错误处理方式,如 'ignore'、'replace' |
2.1 特殊属性(高级定制)
- namer:可调用的命名函数,用于自定义备份文件的名称。
- rotator:可调用的轮转函数,用于自定义轮转行为(如压缩)。
三、Demo 1:基础配置(按大小轮转)
本示例演示最基本的 RotatingFileHandler 配置,当日志文件达到 1KB 时自动轮转,保留 3 个备份。
3.1 Python 代码
import logging
from logging.handlers import RotatingFileHandler
import time
def demo1_basic_rotation():
# 1. 创建 logger
logger = logging.getLogger('demo1')
logger.setLevel(logging.DEBUG)
# 2. 创建 RotatingFileHandler
# 文件大小设为 1KB,保留 3 个备份
handler = RotatingFileHandler(
filename='demo1.log',
mode='a', # 必须使用追加模式
maxBytes=10240, # 10KB
backupCount=3,
encoding='utf-8'
)
# 3. 设置日志格式
formatter = logging.Formatter(
'%(asctime)s – %(name)s – %(levelname)s – %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
handler.setFormatter(formatter)
# 4. 将处理器添加到 logger
logger.addHandler(handler)
# 5. 写入大量日志,触发轮转
for i in range(1, 501):
time.sleep(0.01)
logger.info(f"这是第 {i} 条日志消息,用于测试轮转功能。")
# 每 10 条暂停一点,让输出可读
if i % 100 == 0:
print(f"已写入 {i} 条日志,请查看 demo1.log 及其备份文件")
time.sleep(0.5)
print("Demo1 完成,查看当前目录下的 demo1.log* 文件")
if __name__ == '__main__':
demo1_basic_rotation()
3.2 逐行代码解析
| import logging | 导入核心日志模块 | |
| from logging.handlers import RotatingFileHandler | 导入轮转文件处理器 | |
| logger = logging.getLogger('demo1') | 创建名为 ‘demo1’ 的日志器 | |
| logger.setLevel(logging.DEBUG) | 设置日志器级别为 DEBUG,接受所有级别日志 | |
| handler = RotatingFileHandler(…) | 实例化轮转处理器 | |
| filename='demo1.log' | 指定日志文件名为 demo1.log | |
| mode='a' | 关键:必须使用追加模式,否则轮转时会丢失日志 | |
| maxBytes=10240 | 设置单个文件最大为 10KB,用于快速演示轮转 | |
| backupCount=3 | 保留 3 个备份文件:.1、.2、.3 | |
| encoding='utf-8' | 指定 UTF-8 编码,避免中文乱码 | |
| formatter = logging.Formatter(…) | 创建格式化器,定义日志输出格式 | |
| handler.setFormatter(formatter) | 将格式化器应用到处理器 | |
| logger.addHandler(handler) | 将处理器添加到日志器 | |
| for i in range(1, 501): | 循环写入 500 条日志,确保触发多次轮转 | |
| logger.info(…) | 记录 INFO 级别日志 | |
| time.sleep(0.5) | 短暂暂停,让输出更易观察 |
3.3 运行结果分析
运行后,当前目录会生成如下文件:
demo1.log # 当前正在写入的日志文件
demo1.log.1 # (时间最近的日志)
demo1.log.2
demo1.log.3 # (在轮转顺序中编号最大)(时间最早的日志)
轮转过程:
四、Demo 2:高级定制(自定义命名与压缩)
本示例展示如何使用 namer 和 rotator 实现备份文件的自动压缩(gzip),这是生产环境中节省磁盘空间的常用技巧 。
4.1 Python 代码
import logging
from logging.handlers import RotatingFileHandler
import gzip
import os
import shutil
def demo2_custom_rotation():
"""
演示自定义 namer 和 rotator,实现备份文件自动 gzip 压缩
"""
# 1. 定义命名函数:为备份文件添加 .gz 后缀
def namer(name):
"""
参数 name: 原始备份文件名(如 demo2.log.1)
返回: 压缩后的文件名(如 demo2.log.1.gz)
"""
return name + ".gz"
# 2. 定义轮转函数:对备份文件进行压缩
def rotator(source, dest):
"""
参数 source: 轮转前的原文件路径(如 demo2.log)
参数 dest: 轮转后的目标文件路径(如 demo2.log.1)
此函数会在文件重命名后调用,对 dest 进行压缩
"""
with open(source, 'rb') as f_in:
with gzip.open(dest, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
# 压缩完成后删除原文件
os.remove(source)
# 3. 创建 logger
logger = logging.getLogger('demo2')
logger.setLevel(logging.INFO)
# 4. 创建 RotatingFileHandler
handler = RotatingFileHandler(
filename='demo2.log',
maxBytes=1024, # 1KB,快速演示
backupCount=5,
encoding='utf-8'
)
# 5. 挂载自定义 namer 和 rotator
handler.namer = namer
handler.rotator = rotator
# 6. 设置格式
formatter = logging.Formatter(
'%(asctime)s – %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
# 7. 写入日志,触发轮转
for i in range(1, 301):
logger.info(f"Message no. {i} – 这条日志用于测试压缩轮转。")
if i % 50 == 0:
print(f"已写入 {i} 条日志,查看 demo2.log* 文件")
print("\\nDemo2 完成,生成的文件如下(压缩文件为 .gz):")
for f in os.listdir('.'):
if f.startswith('demo2.log'):
size = os.path.getsize(f)
print(f" {f} – {size} bytes")
if __name__ == '__main__':
demo2_custom_rotation()
4.2 逐行代码解析
| def namer(name): return name + ".gz" | 命名函数:接收原始备份文件名(如 demo2.log.1),返回实际应使用的文件名。此处加上 .gz 后缀。 |
| def rotator(source, dest): | 轮转函数:参数 source 是轮转前的原文件,dest 是目标备份文件名。函数内部使用 gzip.open 压缩 source 的内容写入 dest,然后删除原文件。 |
| handler.namer = namer | 将自定义命名函数赋值给 handler,影响备份文件的命名。 |
| handler.rotator = rotator | 将自定义轮转函数赋值给 handler,替代默认的重命名行为。 |
| with gzip.open(dest, 'wb') as f_out | 以二进制写入模式打开 gzip 文件,shutil.copyfileobj 将原文件内容复制到压缩文件中。 |
| os.remove(source) | 压缩完成后删除未压缩的原文件,节省空间。 |
4.3 运行结果分析
运行后,当前目录会生成如下文件:
demo2.log # 当前正在写入的日志文件
demo2.log.1.gz # 被压缩的备份文件
demo2.log.2.gz
demo2.log.3.gz
demo2.log.4.gz
demo2.log.5.gz
特点:
- 备份文件自动以 .gz 结尾,可以使用 zcat 或 gunzip 查看内容。
- 文件大小显著减小(尤其对于文本日志)。
- 该技术广泛用于生产环境日志管理 。
五、配置注意事项
5.1 mode 参数必须为 'a'
RotatingFileHandler 内部依赖追加模式进行轮转。如果错误地设置为 'w',每次轮转时会清空当前文件内容,导致日志丢失。永远不要使用 'w' 。
5.2 maxBytes 与 backupCount 的关系
- 如果 maxBytes 为 0,轮转永远不会发生(文件无限增长)。
- 如果 backupCount 为 0,轮转发生时只重命名当前文件,不保留任何备份(即旧文件被直接覆盖)。
- 合理设置这两个值可以平衡磁盘占用与历史日志保留需求 。
5.3 文件命名规则
默认情况下,备份文件命名为 原文件名.1、原文件名.2 等,数字越大表示越旧的日志。通过 namer 可以自定义这一规则 。
5.4 多进程安全
RotatingFileHandler 不是多进程安全的。如果在多进程环境中同时写入同一文件,可能会导致日志错乱或轮转失败。解决方案:
- 使用 QueueHandler + QueueListener 将日志集中到单进程处理。
- 使用 concurrent_log_handler 第三方库。
5.5 性能影响
每次写入日志时,RotatingFileHandler 会检查当前文件大小,频繁的 stat 调用可能对极高吞吐量的系统有轻微影响。对于绝大多数应用,这种开销可忽略不计。
六、总结
RotatingFileHandler 是 Python logging 模块中最实用的处理器之一,通过合理的参数配置,可以轻松实现:
| 按大小轮转 | maxBytes + backupCount |
| 保留历史备份 | backupCount 控制数量 |
| 自定义备份名 | namer 函数 |
| 备份压缩 | rotator 函数实现 gzip |
掌握这些配置,你就能构建一个健壮、高效的日志管理系统,既避免磁盘爆满,又保留必要的追溯信息。
如果在学习过程中遇到问题,欢迎在评论区留言讨论!