bigywater.tech
Flask · 185

多进程日志重复问题

日志「重复输出」有好几种完全不同的原因:handler 重复、模块在多个进程里各跑一次、开发服务器的 reloader。多进程写同一个日志文件还可能丢日志。这篇用真实的进程把每种情况分开验证(日志基础见第 181 篇)。

3 次
初始化函数调用 3 次、每次 addHandler 后一条日志的输出次数
3 → 1
gunicorn -w 3 不加 / 加 --preload 时顶层日志行数
8215 / 9000
3 个进程共用一个 RotatingFileHandler 留下的完整行数

handler 重复

实测 · 本机执行输出
  只在 app logger 上加 handler(根 logger 没有)      → 输出 1 次
  根 logger 有 handler 且 app logger 也加了        → 输出 2 次
  两处都有 handler,但 app logger 关了 propagate     → 输出 1 次
  初始化函数被调用了 3 次(每次都 addHandler)              → 输出 3 次
  同上,但加之前判断 handlers 是否已存在                   → 输出 1 次

上一篇:环境变量注入;下一篇:生产环境 DEBUG=False。

多个 worker

实测 · 本机执行输出
  默认(每个 worker 自己导入应用):顶层日志 3 行,pid ['76051', '76052', '76061']
  --preload(master 导入一次再 fork):顶层日志 1 行,pid ['76069']

开发服务器

实测 · 本机执行输出
  顶层日志 2 行,pid ['76077', '76080'](reloader 的父进程和子进程各导入了一次)

多进程写同一个文件

实测 · 本机执行输出
  FileHandler(追加模式):文件 1 个,完整的行 9000 / 9000,损坏的行 0
  RotatingFileHandler(maxBytes=20000,备份保留 200 个,保证不会因备份满而丢):文件 34 个,完整的行 8215 / 9000,损坏的行 0

分步演示

Flask 多进程日志重复
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。gunicorn、Flask 开发服务器、Python logging
    • 没有验证:WatchedFileHandler 配合 logrotate、QueueHandler/QueueListener 汇集多进程日志、SocketHandler 与日志服务、gevent/gthread 下的日志写入、不同 Python 版本的 logging 行为差异;丢失的行数是竞争造成的,每次运行不同
    京ICP备2026067502号-1京公网安备11010502063487号