pythonunbuffered=1是必须设置的环境变量,否则print()和logging.info()日志无法实时输出到docker logs;docker容器无tty导致python全缓冲,需显式配置gunicorn的--access-logfile -和--error-logfile -参数。

PYTHONUNBUFFERED=1 是必须加的,不加就别指望 print() 或 logging.info() 能实时出现在 docker logs 里。
Python 在容器里默认全缓冲,不是 Flask 或 gunicorn 的锅
本地终端运行时,Python 检测到 TTY,stdout 是行缓冲(遇到换行就刷);但 Docker 容器默认无 TTY,Python 自动切为全缓冲——内容得等缓冲区满或进程退出才写出去。而 docker logs 只抓流式 stdout/stderr,没刷出来 = 看不见。
- 现象:容器
ps aux显示进程在跑,但docker logs -f空屏,或只在容器退出后突然刷出一堆日志 - 验证方式:在路由里加
print("test", flush=True),如果不用flush=True就不输出,说明缓冲生效中 - 注意:
sys.stderr默认是无缓冲的,但很多日志库(如 logging)默认输出到stderr,不代表你的print()也自动走 stderr
Dockerfile 中设 PYTHONUNBUFFERED=1 是最稳的写法
环境变量会在整个 Python 进程及其子进程(包括 gunicorn 启动的 worker)中生效,比改启动命令更可靠。
- 必须写在
CMD或ENTRYPOINT之前,例如: ENV PYTHONUNBUFFERED=1CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--access-logfile", "-", "--error-logfile", "-", "app:app"]- 别用
--daemon:它会让 gunicorn fork 后台进程,主进程退出 → 容器立即 stop,日志链断裂 - 如果用 shell 形式
CMD(如CMD python app.py),环境变量仍有效,但不如 exec 形式干净
gunicorn 不打 access log 到 stdout,Docker 就收不到请求日志
gunicorn 默认把 access log 和 error log 写文件,而 Docker 日志驱动只捕获 stdout/stderr。不显式配置,你就只能看到 Flask 启动提示,看不到任何 HTTP 请求记录。
- 必须加两个参数:
--access-logfile -和--error-logfile -(短横线表示 stdout) - 别依赖
gunicorn --capture-output:它只捕获 worker 的 stderr,不解决主进程日志分流问题 - 如果你用了
logging配置了FileHandler,记得删掉或换成StreamHandler(sys.stdout),否则日志又进文件了 - 检查是否误写了
sys.stderr = open("/dev/null", "w")—— 这种代码会让所有 error 日志彻底消失
验证是否真生效的三步法
别只看 Dockerfile 写了没,要亲眼确认日志在流。
- 启动后立刻执行:
docker logs -f --tail 10 <container></container>,确认能看到类似* Running on http://0.0.0.0:5000/的 Flask 提示(说明主进程 stdout 已通) - 发一个请求,观察日志里是否实时出现
172.17.0.1 - - [05/Sep/2026:15:54:00] "GET /health HTTP/1.1" 200 -这类 access log 行 - 在代码里加
print(f"[NOW] {time.time()}", flush=True)和不带flush=True的对比:如果后者也能秒出,说明PYTHONUNBUFFERED生效;如果只有前者有效,说明环境变量没传进实际运行的 Python 进程(常见于多层 shell 包裹或 entrypoint 覆盖)
最容易被忽略的是 gunicorn 的日志重定向和多进程场景下环境变量传递失效——哪怕 PYTHONUNBUFFERED 设对了,gunicorn 子进程若没继承该变量,或者日志根本没往 stdout 写,你还是看不到东西。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











