← 中文资源

简体中文版本

按大小轮转的日志文件

用 RotatingFileHandler 配置按大小切分的日志,保留编号备份、逐条刷新,并打印轮转后的文件列表。

阅读英文原文

功能说明

长期运行的服务如果一直向同一个文件追加日志,文件会无限增长,磁盘写满后进程可能直接失败。按大小轮转是常见做法:写入前检查当前文件大小,达到阈值就把文件改名为带编号的备份,再重新创建主文件,超过保留数量的最旧备份被删除。Python 标准库的 RotatingFileHandler 已经实现这套逻辑,只需配置 maxBytes 和 backupCount。

本例把处理器构建封装为函数,设置固定的日志格式、关闭向父级传播,并在重复调用时复用已有处理器,避免同一条日志被写多次。演示程序写入十二条记录,阈值设为三百字节,因此会触发多次轮转。

关键代码

build_logger 先创建父目录,确保路径不存在时也不会失败。getLogger 按名称取得同一个记录器,setLevel 设为信息级。判断 handlers 为空后再添加,防止热重载或测试重复初始化造成重复输出。RotatingFileHandler 接收路径、字节阈值、备份数量和编码,编码显式设为 UTF-8,避免平台默认编码导致中文乱码。格式化器包含时间、级别和消息。主程序写入后主动 flush,再列出匹配 app.log 与 app.log.1 等备份的文件,最后清理演示目录。

使用注意

RotatingFileHandler 不保证多进程安全,多个工作进程同时写同一个文件可能互相覆盖或丢失轮转,应该让每个进程写独立文件,或改用支持进程锁的处理器。轮转发生在写入时,单个超大日志可能让文件短时超过阈值。备份数量乘以阈值就是磁盘占用上限,需要与磁盘配额一起评估。日志中不要写入密钥、令牌和完整个人数据。容器环境下更适合输出到标准输出,由运行时负责收集和轮转。

验证方式

运行脚本,files 列表包含 app.log、app.log.1 和 app.log.2,说明文件按大小切分且只保留两个备份。latest 打印最后一条包含 request 11 的日志,证明最新记录仍在主文件。可把 max_bytes 调大后重跑,观察备份数量减少。

代码示例

import logging
import shutil
from logging.handlers import RotatingFileHandler
from pathlib import Path


def build_logger(
    path: str, *, max_bytes: int = 1_000_000, backups: int = 3
) -> logging.Logger:
    target = Path(path)
    target.parent.mkdir(parents=True, exist_ok=True)
    logger = logging.getLogger("demo.rotation")
    logger.setLevel(logging.INFO)
    logger.propagate = False
    if not logger.handlers:
        handler = RotatingFileHandler(
            target, maxBytes=max_bytes, backupCount=backups, encoding="utf-8"
        )
        handler.setFormatter(
            logging.Formatter("%(asctime)s %(levelname)s %(message)s")
        )
        logger.addHandler(handler)
    return logger


if __name__ == "__main__":
    log = build_logger("build/logs/app.log", max_bytes=300, backups=2)
    for index in range(12):
        log.info("request %d completed", index)
    for handler in log.handlers:
        handler.flush()
        handler.close()
    log.handlers.clear()
    files = sorted(path.name for path in Path("build/logs").glob("app.log*"))
    print("files:", files)
    print("latest:", Path("build/logs/app.log").read_text(encoding="utf-8").splitlines()[-1])
    shutil.rmtree("build")