Реальный пример: сбор логов веб-приложения

Соберём всё в один конец: настроим полный конвейер логов типичного веб-приложения от файла до алерта.

Цель — провести один реальный сценарий целиком: nginx-фронт + backend на Python пишут логи, мы собираем их в ELK, ищем, строим дашборд и ставим алерт.

Постановка

Дано: веб-приложение из nginx (отдаёт статику и проксирует) и backend-сервиса на Python. nginx пишет access-лог в привычном текстовом формате, backend настроен на структурированный JSON-вывод. Хотим: видеть все запросы и ошибки в Kibana, дашборд состояния и оповещение при всплеске 5xx.

Шаг 1: backend пишет структурированные логи

Лучшее решение — структурированные логи (раздел 5). Настраиваем Python-логгер на JSON, чтобы не парсить grok'ом. Каждая строка лога backend выглядит так:

{"@timestamp":"2026-06-24T14:32:07Z","log.level":"ERROR","service.name":"backend","message":"db query failed","url.path":"/api/orders","http.response.status_code":500,"trace_id":"a1b2c3d4"}

Поля сразу по ECS: service.name, http.response.status_code, trace_id. Парсить нечего.

Шаг 2: Filebeat собирает оба источника

На веб-сервере ставим Filebeat с двумя входами: nginx (текст — разберёт модуль) и backend (JSON — декодируем). Для backend включаем парсер ndjson, для стектрейсов — multiline (на случай редких текстовых ошибок):

filebeat.inputs:
  - type: filestream
    id: backend-json
    paths:
      - /var/log/backend/app.log
    parsers:
      - ndjson:
          target: ""
          overwrite_keys: true

filebeat.modules:
  - module: nginx
    access:
      var.paths: ["/var/log/nginx/access.log"]

output.logstash:
  hosts: ["logstash01:5044"]

Шаг 3: Logstash обрабатывает и маршрутизирует

Logstash приводит время, добавляет geoip по IP nginx и шлёт в разные индексы по типу источника:

filter {
  date { match => [ "@timestamp", "ISO8601" ] }
  if [source][ip] {
    geoip { source => "[source][ip]" target => "geo" }
  }
}
output {
  if [service][name] == "backend" {
    elasticsearch { hosts => ["es01:9200"] index => "logs-backend-%{+YYYY.MM.dd}" }
  } else {
    elasticsearch { hosts => ["es01:9200"] index => "logs-nginx-%{+YYYY.MM.dd}" }
  }
}

Шаг 4: индексы, шаблон, ILM

Заранее заводим index template на logs-* с правильным маппингом (раздел 7): http.response.status_code — integer, trace_id и service.name — keyword, привязка к ILM-политике (rollover 50 ГБ/1 день, delete через 30 дней). Так новые дневные индексы рождаются правильными и сами стареют.

Шаг 5: смотрим в Kibana

Создаём Data View logs-* с полем времени @timestamp. В Discover проверяем, что логи идут. Собираем дашборд: ошибки во времени, топ URL по 5xx, карта запросов по geo.location, распределение статусов. Теперь состояние приложения видно одним взглядом.

  ПОТОК ЦЕЛИКОМ:
  nginx access.log ─┐
                    ├─ Filebeat ─> Logstash ─> ES (logs-*) ─> Kibana
  backend app.log ──┘   (сбор)    (date,geoip,    (индексы      (Discover,
   (JSON)                          маршрут)        по дням+ILM)   дашборд, алерт)

Шаг 6: алерт

Ставим правило (раздел 7): «доля http.response.status_code >= 500 за 5 минут превысила 5%» → коннектор в Slack дежурной команды. Теперь при всплеске ошибок мы узнаём первыми, а в логах уже есть trace_id, чтобы тут же начать разбор по методике раздела 8.

Эволюция этой установки

Описанная конфигурация — рабочая отправная точка, но полезно представлять, как она растёт. Сначала, при малом потоке, можно даже опустить Logstash: Filebeat пишет в Elasticsearch напрямую, geoip и приведение делает ingest-пайплайн, и это полностью закрывает потребности маленького проекта. По мере роста добавляют Logstash для сложной обработки, затем Kafka между сбором и обработкой, чтобы пережить всплески и сбои ES. Когда сервисов становится много, появляется единый стандарт полей (ECS) и сквозной trace_id, прокинутый от nginx через backend во все зависимости. А когда приложение переезжает в Kubernetes, точечные Filebeat на серверах сменяются сборщиком-DaemonSet на каждом узле. Архитектура логирования — живая и должна расти вместе с системой, а не быть зафиксированной раз и навсегда.

Стоит также сразу заложить то, что больно добавлять задним числом. Сквозной trace_id легко предусмотреть на старте и мучительно внедрять, когда сервисов уже двадцать. Единые имена полей дёшевы, пока сервисов мало, и дороги при переделке десятка разнобойных форматов. ILM с фазой delete стоит включить с первого дня, а не когда диск уже заполнен. Эти решения почти ничего не стоят в начале и спасают много сил потом — закладывайте их сразу, даже если проект пока маленький.

Частые ошибки этого сценария

  • Backend в тексте, а не JSON. Парсить разнородные строки backend grok'ом — лишняя хрупкость. Если контролируете backend — включайте JSON-логи.
  • Нет общего trace_id. Без сквозного идентификатора красивый дашборд есть, а связать nginx-запрос с ошибкой backend нельзя. Прокидывайте trace_id от фронта вглубь.
  • Забыли ILM с самого начала. Без политики удаления через месяц-другой диск кончится — поставьте ILM до того, как пойдёт прод-объём.

Итоги

  • Сквозной конвейер: nginx (текст, модуль) + backend (JSON) → Filebeat → Logstash (date/geoip/маршрут) → ES (индексы по дням + ILM) → Kibana (Data View, дашборд, алерт).
  • Структурированные JSON-логи backend по ECS избавляют от хрупкого grok.
  • Сквозной trace_id связывает nginx и backend и даёт разбор инцидента в один запрос.
  • Index template + ILM с самого старта гарантируют правильный маппинг и контроль над диском.
Проверьте себя
1. Почему в примере backend настраивают на JSON-логи, а не на текстовые?
AJSON меньше весит
BСтруктурированные логи по ECS приходят с готовыми полями и не требуют хрупкого grok-парсинга
Cnginx не умеет работать с текстом
DJSON быстрее пишется на диск
2. Что в этом сценарии позволяет связать запрос в nginx с ошибкой в backend?
AОдинаковое имя индекса
BСквозной trace_id, прокинутый от фронта вглубь и попавший в логи обоих
CОбщий часовой пояс
DОдинаковый формат строки
3. Почему ILM нужно настроить с самого начала конвейера?
AИначе логи не будут искаться
BИначе через месяц-другой индексы заполнят диск, ведь без политики удаления они растут бесконечно
CИначе не запустится Logstash
DИначе Kibana не покажет дашборд