Cane's Blog

Cane

【Problem】Python 日志输出刷新不及时

29
2021-01-17

问题描述

FROM python:3
ADD . /code
WORKDIR /code
RUN pip3 install -r requirements.txt -i https://pypi.douban.com/simple
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
CMD python scheduler.py

Dockerfile 如上,发现容器正常运行,但是执行docker logs xx没有日志输出

截图

...

解决方案

原因:

Python 的 print 输出默认写入 stdout 缓冲,只有总量达到 「4k」后才会全部打印出来(Pycharm蔽了这种缓冲,所以在 Pycharm 中调试时并不会发现这样的问题)

  1. 使用 -u 命令行开关

    python -u scheduler.py

    -u 可能的不适用情况:不适用于已编译的字节码或以 __main__.py 文件为入口点的应用程序

    方案 2 和方案 5 可以避免这种情况

  2. 包装 sys.stdout 每次写入后刷新的对象

    import functools
    print = functools.partial(print, flush=True)
  3. 设置环境 PYTHONUNBUFFERED 变量

    class Unbuffered(object):
       def __init__(self, stream):
           self.stream = stream
       def write(self, data):
           self.stream.write(data)
           self.stream.flush()
       def writelines(self, datas):
           self.stream.writelines(datas)
           self.stream.flush()
       def __getattr__(self, attr):
           return getattr(self.stream, attr)
    
    import sys
    sys.stdout = Unbuffered(sys.stdout)
    print 'Hello'<br>
    # 原始sys.stdout仍可作为sys.__ stdout__获得
  4. sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

    import io, os, sys
    try:
        # Python 3, open as binary, then wrap in a TextIOWrapper with write-through.
        sys.stdout = io.TextIOWrapper(open(sys.stdout.fileno(), 'wb', 0), write_through=True)
        # If flushing on newlines is sufficient, as of 3.7 you can instead just call:
        # sys.stdout.reconfigure(line_buffering=True)
    except TypeError:
        # Python 2
        sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
  5. 使用需要立刻输出的日志的地方,给 print 添加 flush=True 参数(类似于2)

    print('right', flush=True)  # python 3.3版本以上生效

关于此问题的一些讨论:

  1. Capturing output of python script run inside a docker container

  2. Python Disable Output Buffering

  3. CPython中关于Output Buffering的相关代码