默认 rotatingfilehandler 仅支持按大小轮转,不支持按天切分,且多进程下因文件竞争导致日志丢失或 permissionerror;需用 timedrotatingfilehandler(设 when='midnight'、backupcount=7)并先 clear() 原 handler 避免重复。

为什么默认的 RotatingFileHandler 不够用?
Flask 本身不管理日志滚动策略,它依赖 Python 标准库的 logging 模块。很多人直接套用 RotatingFileHandler,结果发现日志没按天切分、旧日志没自动清理、或者多进程下文件被锁死——根本原因是这个 handler 只支持按大小轮转,不支持按时间(比如每天一个文件),且在 gunicorn/uwsgi 多 worker 场景下会因文件竞争导致写入丢失或报错 PermissionError 或 BlockingIOError。
真正需要的是:TimedRotatingFileHandler(按时间切分) + 进程安全封装(避免多进程写冲突) + 显式关闭旧 handler(防止重复添加)。
如何正确配置 TimedRotatingFileHandler 并避免重复添加?
Flask 的 app.logger 是个现成的 logger 实例,但每次重载或测试时反复调用 app.logger.addHandler(...) 会导致日志重复输出。必须先清空已有 handler,再添加新 handler。
- 用
app.logger.handlers.clear()彻底移除默认的StreamHandler -
TimedRotatingFileHandler的when='midnight'表示每天零点切分,backupCount=7表示最多保留 7 天日志 - 务必设置
encoding='utf-8',否则中文日志可能乱码 - 避免使用
delay=True,它可能导致首次写日志时创建失败
from logging.handlers import TimedRotatingFileHandler import logging <p>handler = TimedRotatingFileHandler( filename='logs/app.log', when='midnight', interval=1, backupCount=7, encoding='utf-8', delay=False ) handler.setFormatter(logging.Formatter( '%(asctime)s %(name)s %(levelname)s %(message)s' )) app.logger.handlers.clear() app.logger.addHandler(handler) app.logger.setLevel(logging.INFO)</p>
多进程部署(gunicorn)下如何避免日志写入冲突?
TimedRotatingFileHandler 本身不是进程安全的:多个 gunicorn worker 同时尝试 rename 或 delete 旧日志文件,会触发 OSError: [Errno 16] Device or resource busy 或静默丢日志。
可行解只有两个:
- 让所有 worker 写入同一个文件(不推荐,高并发下 I/O 瓶颈 + 无序交错)
- 改用
ConcurrentLogHandler(需安装pip install concurrent-log-handler),它用文件锁保证安全
from concurrent_log_handler import ConcurrentRotatingFileHandler <p>handler = ConcurrentRotatingFileHandler( 'logs/app.log', maxBytes=10<em>1024</em>1024, # 10MB backupCount=5, encoding='utf-8' )</p>
注意:ConcurrentRotatingFileHandler 不支持 when='midnight',只能按大小切分;若硬要按天,得配合外部脚本或用 watchdog 监听时间触发 rotate。
日志路径不存在时为什么静默失败?
TimedRotatingFileHandler 和 ConcurrentRotatingFileHandler 都不会自动创建父目录。如果 logs/ 目录不存在,你会看到没有任何日志产生,也没有报错——因为 handler 初始化时只检查文件,不检查路径。
必须手动确保目录存在:
- 启动前执行
os.makedirs('logs', exist_ok=True) - 或在 handler 初始化前加一层判断:
os.path.dirname(filename)→os.makedirs(..., exist_ok=True)
最常被忽略的其实是权限问题:部署到 Linux 生产环境时,运行 gunicorn 的用户(如 www-data)可能没有 logs/ 目录的写权限,导致 handler 创建失败后降级为不写日志,而 Flask 默认又不抛异常。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











