首页 > 解决方案 > 在上下文中使用大型生成器流式传输 Django 模板

问题描述

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

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

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

我的 Django 应用程序正在运行 gunicorn 和 nginx(下面的配置)。

我可以看到设置了相关的响应标头,所以我不知道为什么日志在大约 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:

标签: djangostreaminggunicorn

解决方案


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

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

相反,您可以加载父模板,并在其中进行 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放在 a<pre>中,所以我只是按原样渲染日志文本:

{{ log_line }}

推荐阅读