Реальный пример: сбор логов веб-приложения
Соберём всё в один конец: настроим полный конвейер логов типичного веб-приложения от файла до алерта.
Цель — провести один реальный сценарий целиком: 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 с самого старта гарантируют правильный маппинг и контроль над диском.