Потоковый шаблон Django с большим генератором в контексте - PullRequest
0 голосов
/ 12 февраля 2020

Я хочу визуализировать большие файлы журнала в шаблоне 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 -композит:

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:

1 Ответ

0 голосов
/ 12 февраля 2020

Я нашел способ сделать это, но в процессе понял, что пытаться отображать большие файлы журналов в браузере - плохая идея, поскольку некоторые из них действительно очень большие.

На случай, если кто-то пытаясь сделать это (с немного меньшими файлами), обратите внимание, что шаблоны в основном отображаются синхронно, поэтому вышеприведенный подход на самом деле не работает.

Вместо этого вы можете загрузить родительский шаблон, и там сделать 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 }}
...