功能说明
长期运行的服务如果一直向同一个文件追加日志,文件会无限增长,磁盘写满后进程可能直接失败。按大小轮转是常见做法:写入前检查当前文件大小,达到阈值就把文件改名为带编号的备份,再重新创建主文件,超过保留数量的最旧备份被删除。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")