在上下文中使用大型生成器流式传输 Django 模板

Streaming Django template with large generator in context

我想在 Django 模板中呈现大型日志文件。

为此,模板呈现由从磁盘流式传输日志文件的生成器提供的各个行。

模板渲染在一段时间后停止,我不确定哪里出错了。

我的 Django 应用程序是 运行 gunicorn 和 nginx(配置如下)。

我可以看到相关响应 headers 已设置,所以我不知道为什么日志在 ~30-40 秒内停止呈现:

HTTP/1.1 200 OK
Server: nginx/1.17.4
Date: Wed, 12 Feb 2020 12:53:43 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Frame-Options: SAMEORIGIN

模板相关部分:

<div class="log-body">
{% for line in log %}
  {{ line }}
{% endfor %}
</div>

views.py

中的渲染函数
def render_log(request):
    log = read_log_file_gen("some_path.log")

    if not log:
        log = ["No logs found"]

    template = loader.get_template('show_log.html')
    context = {
        'log': log,
    }

    return StreamingHttpResponse(template.render(context, request))

def read_log_file_gen(path):
    _256KB = 256 * 1024
    try:
        with open(path) as f:
            while True:
                lines = f.readlines(_256KB)
                if not lines:
                    break
                yield "".join(lines)
    except IOError:
        return None

应用贯穿docker-compose:

version: '3'

services:
  web:
    build: ./app
    command: gunicorn app.wsgi:application --bind 0.0.0.0:8000 -k gevent
    volumes:
      - static_volume:/usr/src/app/static
      - /var/run/docker.sock:/var/run/docker.sock
      - /mnt/data:/mnt/data
    expose:
      - 8000
    env_file:
      - ./env/.env.prod
  nginx:
    build: ./nginx
    volumes:
      - static_volume:/usr/src/app/static
    ports:
      - 8888:80
    depends_on:
      - web

volumes:
  static_volume:

我找到了一种方法来做到这一点,但在这个过程中我意识到尝试在浏览器中显示大日志文件是个坏主意,因为其中一些确实非常大。

万一有人尝试这样做(大文件稍微少一点),请注意模板基本上是同步呈现的,因此上述方法实际上不起作用。

相反,您可以加载父模板,并在其中进行 AJAX 调用:

<script>
    $(document).ready(function () {
        $.ajax({
            type: "GET",
            url: "{% url 'myapp:logs' log.id %}",
            dataType: "text/plain",
            success: function (logText) {
                $("#logText").replaceWith(logText)
            }
        });
</script>

url myapp:logs 处的函数然后可以使用 StreamingHttpResponse:

from django.template import loader


def log(request, log_id):
    log_gen = read_log_file_gen(f"{log_id}.log")
    template = loader.get_template('mytemplates/log.html')
    return StreamingHttpResponse(log_render_gen(template, log_gen))


def log_render_gen(template, log_gen):
    for log in log_gen:
        yield template.render({'log_line': log})


def read_log_file_gen(path):
    _256KB = 256 * 1024
    try:
        with open(path) as f:
            while True:
                lines = f.readlines(_256KB)
                if not lines:
                    break
                yield "".join(lines)
    except IOError:
        return None

呈现 log.html 的异步结果放在 <pre> 中,所以我只按原样呈现日志文本:

{{ log_line }}