Мониторинг логов Nginx: сбор и анализ access.log и error.log с Loki, Promtail и Grafana | AdminWiki

Мониторинг логов Nginx: сбор и анализ access.log и error.log с Loki, Promtail и Grafana

25 июля 2026 12 мин. чтения
Содержание статьи

Разрозненные логи на серверах превращают диагностику ошибок в хаос. Вы теряете время, переключаясь между машинами, и рискуете пропустить критический сбой. Стек Loki, Promtail и Grafana решает эту проблему: вы получаете единую точку сбора, структурирования и визуализации логов Nginx без потери производительности. В этом руководстве вы настроите централизованный мониторинг, научитесь парсить access.log и error.log, писать запросы LogQL для поиска ошибок 4xx/5xx и строить дашборды для анализа частоты запросов и аномалий.

Loki спроектирован как легковесная альтернатива Elasticsearch. Он индексирует только метки, а сами логи хранит в сжатом виде в объектном хранилище. Это снижает накладные расходы на инфраструктуру в несколько раз по сравнению с ELK-стеком. Интеграция с Grafana даёт единый интерфейс для метрик и логов, а язык запросов LogQL, построенный на базе PromQL, интуитивно понятен DevOps-инженерам. Если вы уже используете Prometheus для сбора метрик Nginx, добавление Loki станет естественным расширением вашего мониторингового стека.

Зачем централизовать логи Nginx и почему именно стек Loki

На production-сервере с Nginx ежесекундно генерируются десятки записей в access.log и error.log. Когда серверов становится больше одного, grep по локальным файлам перестаёт работать. Вы не можете оперативно сопоставить события на разных узлах, а хранение логов на самих серверах ограничено дисковым пространством. Централизованный сбор решает эти задачи: все логи стекаются в одно хранилище, доступны для поиска и визуализации из единого интерфейса.

Loki выигрывает у классического ELK-стека по трём параметрам: стоимость хранения, простота эксплуатации и интеграция с Grafana. Elasticsearch требует мощных серверов с быстрыми дисками для индексации каждого слова в логах. Loki же индексирует только метки - например, job, host, filename - а тело логов хранит как есть, сжимая блоками. В результате терабайт логов в Loki занимает меньше места и требует меньше ресурсов на обслуживание. Для команды из двух-трёх DevOps-инженеров это критично: вы не тратите время на тюнинг JVM и перестроение индексов.

Promtail, агент сбора логов, работает по тому же принципу, что и Promtail в экосистеме Prometheus: он читает файлы, обогащает записи метками и отправляет в Loki. Никаких посредников вроде Logstash не требуется. Настройка сводится к одному YAML-файлу. Grafana замыкает стек, предоставляя визуализацию и алертинг. Вы можете на одном дашборде видеть график запросов из логов Nginx и метрики использования CPU из Prometheus, сопоставляя всплеск трафика с ростом нагрузки на сервер.

Архитектура решения: как Promtail, Loki и Grafana работают вместе

Поток данных выглядит так: Nginx записывает события в access.log и error.log на сервере. Promtail, запущенный на том же хосте, отслеживает изменения в этих файлах, парсит записи согласно pipeline_stages, добавляет метки и отправляет их по HTTP в Loki. Loki сохраняет логи в хранилище - локальный диск, S3-совместимое объектное хранилище или Google Cloud Storage. Grafana подключается к Loki как к источнику данных и выполняет запросы LogQL, отображая результаты на панелях дашборда.

Каждый компонент выполняет чётко ограниченную функцию. Promtail - это сборщик и парсер. Он не буферизует логи на диск, а отправляет их сразу в Loki, что упрощает архитектуру. Loki - это хранилище и движок запросов. Он принимает данные, сжимает их и обслуживает запросы LogQL. Grafana - это слой визуализации. Она не хранит логи и не выполняет их предобработку, а лишь отображает результаты запросов к Loki. Такое разделение позволяет масштабировать компоненты независимо: добавить больше экземпляров Promtail на новые серверы, увеличить ёмкость хранилища Loki или развернуть отдельный инстанс Grafana для команды разработки.

Важный архитектурный нюанс: метки в Loki определяют гранулярность поиска. Метки с высокой кардинальностью - например, request_id - создают отдельные потоки и быстро деградируют производительность. Хорошая практика - ограничиться метками уровня хоста, имени файла и окружения, а детали запроса извлекать через парсинг и фильтровать в LogQL.

Установка и базовая настройка Loki и Promtail

Начнём с развёртывания Loki и Promtail на сервере с Nginx. Используем Docker - это самый быстрый способ получить работающий стек. Убедитесь, что Docker установлен и запущен. Создайте директорию проекта /opt/loki и разместите в ней конфигурационные файлы.

Запуск Loki: конфигурация и проверка

Создайте файл loki-config.yaml с минимальной рабочей конфигурацией:

auth_enabled: false

server:
  http_listen_port: 3100
  grpc_listen_port: 9096

common:
  instance_addr: 127.0.0.1
  path_prefix: /tmp/loki
  storage:
    filesystem:
      chunks_directory: /tmp/loki/chunks
      rules_directory: /tmp/loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory

schema_config:
  configs:
    - from: 2024-01-01
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

limits_config:
  allow_structured_metadata: true

Эта конфигурация запускает Loki в single-binary режиме с хранением на локальной файловой системе. Для production-среды замените filesystem на объектное хранилище - S3, GCS или Azure Blob Storage. Параметр allow_structured_metadata: true включает поддержку структурированных метаданных, которые понадобятся при парсинге логов.

Запустите Loki командой:

docker run -d --name loki \
  -p 3100:3100 \
  -v /opt/loki/loki-config.yaml:/etc/loki/loki-config.yaml \
  grafana/loki:3.0.0 \
  -config.file=/etc/loki/loki-config.yaml

Проверьте, что Loki отвечает на запросы:

curl http://localhost:3100/ready

Ответ Ready означает, что сервис запущен и готов принимать данные.

Настройка Promtail для отправки логов в Loki

Создайте файл promtail-config.yaml:

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://localhost:3100/loki/api/v1/push

scrape_configs:
  - job_name: nginx
    static_configs:
      - targets:
          - localhost
        labels:
          job: nginx
          host: webserver-01
          __path__: /var/log/nginx/access.log
      - targets:
          - localhost
        labels:
          job: nginx
          host: webserver-01
          __path__: /var/log/nginx/error.log

Разберём ключевые секции. clients указывает URL Loki для отправки логов. scrape_configs определяет, какие файлы читать и какие метки к ним добавлять. Директива __path__ задаёт путь к лог-файлу внутри контейнера Promtail. Метки job и host будут прикреплены к каждой записи и позволят фильтровать логи в запросах LogQL.

Запустите Promtail, примонтировав директорию с логами Nginx:

docker run -d --name promtail \
  -v /opt/loki/promtail-config.yaml:/etc/promtail/promtail-config.yaml \
  -v /var/log/nginx:/var/log/nginx \
  -v /tmp:/tmp \
  grafana/promtail:3.0.0 \
  -config.file=/etc/promtail/promtail-config.yaml

Проверьте, что Promtail стартовал без ошибок:

docker logs promtail 2>&1 | grep -i error

Отсутствие вывода означает, что агент успешно подключился к Loki и начал отправку логов. Теперь все новые записи в access.log и error.log будут попадать в Loki в режиме реального времени.

Парсинг логов Nginx в Promtail: извлечение ключевых метрик

По умолчанию Promtail отправляет каждую строку лога как одно текстовое поле. Чтобы фильтровать по HTTP-статусам, URI или времени ответа, нужно распарсить строку на структурированные поля. В Promtail за это отвечают pipeline_stages - цепочка обработчиков, применяемых к каждой записи.

Парсинг access.log: извлечение HTTP-статусов, URI и времени запроса

Стандартный комбинированный формат лога Nginx выглядит так:

192.168.1.10 - - [25/Jul/2026:10:15:30 +0300] "GET /api/users HTTP/1.1" 200 1234 "-" "Mozilla/5.0"

Добавьте в секцию scrape_configs для access.log блок pipeline_stages:

scrape_configs:
  - job_name: nginx
    static_configs:
      - targets:
          - localhost
        labels:
          job: nginx
          host: webserver-01
          filename: access.log
          __path__: /var/log/nginx/access.log
    pipeline_stages:
      - regex:
          expression: '^(?P<remote_addr>\S+) \S+ \S+ \[(?P<timestamp>[^\]]+)\] "(?P<method>\S+) (?P<uri>\S+) \S+" (?P<status>\d{3}) (?P<body_bytes_sent>\d+) "(?P<http_referer>[^"]*)" "(?P<http_user_agent>[^"]*)" (?P<request_time>\S+)?'
      - timestamp:
          source: timestamp
          format: "02/Jan/2006:15:04:05 -0700"
      - labels:
          status:
          method:
          uri:

Разберём, что здесь происходит. Regex-выражение захватывает именованные группы: remote_addr, timestamp, method, uri, status, body_bytes_sent, http_referer, http_user_agent, request_time. Стадия timestamp парсит временную метку и заменяет ей время записи в Loki - это критично для корректного отображения на графиках. Стадия labels создаёт метки Loki из полей status, method и uri. Используйте метки осторожно: поля с высокой кардинальностью, такие как uri, могут создать миллионы потоков в Loki. Для production-среды лучше вынести uri в структурированные метаданные, а метками оставить только status и method.

Если ваш формат лога отличается - например, вы используете кастомный log_format с дополнительными полями - скорректируйте регулярное выражение. Проверить парсинг можно, запустив Promtail с флагом -dry-run и подав на вход тестовую строку.

Обработка error.log: выделение уровня ошибки и сообщения

Формат error.log менее структурирован. Типичная запись выглядит так:

2026/07/25 10:15:30 [error] 12345#12345: *67890 upstream timed out (110: Connection timed out) while connecting to upstream, client: 192.168.1.10, server: example.com, request: "GET /api/slow HTTP/1.1", upstream: "http://10.0.1.5:8080/api/slow", host: "example.com"

Добавьте отдельный scrape_config для error.log:

- targets:
    - localhost
  labels:
    job: nginx
    host: webserver-01
    filename: error.log
    __path__: /var/log/nginx/error.log
  pipeline_stages:
    - regex:
        expression: '^(?P<timestamp>\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[(?P<level>\w+)\] \S+: (?P<message>.*)$'
    - timestamp:
        source: timestamp
        format: "2006/01/02 15:04:05"
    - labels:
        level:

Regex извлекает три поля: timestamp, level (error, warn, notice) и message - полный текст ошибки. Метка level позволит быстро отфильтровать только критические ошибки в запросах LogQL. Если в error.log попадают сообщения разных форматов - например, от PHP-FPM или FastCGI - создайте несколько pipeline_stages с разными regex и условием match.

После обновления конфигурации перезапустите Promtail:

docker restart promtail

Проверьте, что структурированные поля появились в Loki, выполнив запрос в Grafana (настройку источника данных рассмотрим далее):

{job="nginx", filename="access.log"} | json

Язык запросов LogQL: основы и практические примеры для Nginx

LogQL состоит из двух частей: stream selector, который фильтрует потоки логов по меткам, и log pipeline, который фильтрует и преобразует содержимое записей. Базовый синтаксис:

{метка="значение"} | оператор

Stream selector {job="nginx"} выберет все логи с меткой job=nginx. Добавив {job="nginx", filename="access.log"}, вы ограничите выборку только логом доступа. Log pipeline начинается с символа | и может включать фильтры (|=, !=, |~, !~), парсеры (| json, | logfmt, | unpack) и агрегации.

Поиск ошибок 4xx и 5xx с помощью LogQL

После настройки парсинга access.log поле status становится доступно для фильтрации. Запрос для поиска всех ошибок 5xx:

{job="nginx", filename="access.log"} | json | status >= 500

Оператор | json парсит JSON-представление записи, которое Loki создаёт автоматически при наличии структурированных метаданных. Если вы создали метку status через pipeline_stages, можно использовать более быстрый запрос без парсинга:

{job="nginx", filename="access.log", status=~"5.."}

Регулярное выражение "5.." выбирает все статусы, начинающиеся с 5. Аналогично для 4xx:

{job="nginx", filename="access.log", status=~"4.."}

Чтобы подсчитать количество ошибок каждого типа за последний час, используйте агрегацию:

sum by (status) (
  count_over_time(
    {job="nginx", filename="access.log"} | json | status >= 400 [1h]
  )
)

Этот запрос вернёт таблицу: статус - количество записей. Вы увидите, что 404 встречается в десять раз чаще, чем 500, и сможете приоритизировать исправление.

Агрегация и анализ трендов: частота запросов и топ URI

Функция rate() вычисляет количество записей в секунду за указанный интервал. График общего числа запросов к Nginx:

rate({job="nginx", filename="access.log"}[1m])

Этот запрос показывает трафик в запросах в секунду, усреднённый за минутное окно. На дашборде Grafana он превратится в классический график нагрузки на веб-сервер.

Топ-10 запрашиваемых URI за последний час:

topk(10,
  sum by (uri) (
    count_over_time(
      {job="nginx", filename="access.log"} | json [1h]
    )
  )
)

Функция topk() оставляет k наибольших значений из агрегации. sum by (uri) группирует записи по URI и суммирует количество. Результат - список из десяти самых популярных эндпоинтов с частотой запросов. Это помогает выявить неожиданно нагруженные страницы или API-методы, которые потребляют непропорционально много ресурсов.

Для поиска медленных запросов используйте фильтрацию по извлечённому полю request_time:

{job="nginx", filename="access.log"} | json | request_time > 1.0

Этот запрос найдёт все запросы, которые выполнялись дольше одной секунды. Добавив агрегацию, можно получить среднее время ответа по URI:

avg by (uri) (
  {job="nginx", filename="access.log"} | json | unwrap request_time [5m]
)

Оператор unwrap извлекает числовое значение из поля request_time, делая его доступным для математических агрегаций.

Визуализация в Grafana: создание дашборда для мониторинга Nginx

Подключите Loki как источник данных в Grafana. Перейдите в Configuration → Data Sources → Add data source, выберите Loki и укажите URL http://localhost:3100. Нажмите Save & test - Grafana проверит соединение и вернёт зелёный статус.

Создайте новый дашборд и начните добавлять панели. Каждая панель - это один или несколько запросов LogQL с настройками визуализации.

Панель «Частота запросов»: мониторинг нагрузки

Добавьте панель типа Time series. В поле запроса введите:

rate({job="nginx", filename="access.log"}[1m])

Настройте легенду: {{host}} - запросов/с. В разделе Standard options установите единицу измерения reqps (requests per second). График покажет динамику трафика. Резкий рост в нерабочее время может указывать на DDoS-атаку или сбойный клиент, бесконечно ретраящий запросы. Падение до нуля при отсутствии плановых работ - сигнал, что Nginx упал или Promtail потерял соединение.

Панель «Ошибки 4xx/5xx»: оперативное выявление проблем

Создайте панель типа Time series с двумя запросами:

rate({job="nginx", filename="access.log", status=~"4.."}[1m])
rate({job="nginx", filename="access.log", status=~"5.."}[1m])

Назначьте разный цвет для каждого запроса: жёлтый для 4xx, красный для 5xx. В разделе Thresholds добавьте порог для 5xx - например, значение выше 0.1 подсвечивается красным фоном. Это привлечёт внимание оператора к всплеску серверных ошибок. Дополните панель запросом, показывающим долю ошибок от общего числа запросов:

sum(rate({job="nginx", filename="access.log", status=~"[45].."}[1m]))
/
sum(rate({job="nginx", filename="access.log"}[1m])) * 100

Этот запрос возвращает процент ошибочных запросов. Значение выше 5% в течение нескольких минут - повод для немедленного расследования.

Панель «Топ запрашиваемых URI»: анализ популярности страниц

Добавьте панель типа Bar gauge или Table. Запрос:

topk(10,
  sum by (uri) (
    count_over_time(
      {job="nginx", filename="access.log"} | json [1h]
    )
  )
)

Настройте отображение: Orientation - Horizontal, Display mode - Gradient. Панель покажет десять самых популярных URI с цветовой индикацией относительной нагрузки. Это полезно для выявления неоптимизированных страниц: если эндпоинт /api/report генерирует столько же запросов, сколько главная страница, но при этом каждый запрос выполняется 5 секунд, вы нашли кандидата на кэширование или оптимизацию.

Соберите эти панели на одном дашборде, добавьте фильтр по host для переключения между серверами и настройте автообновление каждые 30 секунд. Вы получите оперативный центр управления логами Nginx, который можно вывести на отдельный монитор в офисе или открыть в мобильной Grafana при ночном дежурстве.

Выявление аномалий и алертинг на основе логов Nginx

Дашборд показывает текущее состояние, но не может заменить алертинг. Grafana позволяет создавать правила алертинга на основе запросов LogQL и отправлять уведомления в Telegram, Slack, PagerDuty или любой webhook-совместимый сервис.

Создайте alert rule для обнаружения всплеска ошибок 5xx. Перейдите в Alerting → Alert rules → Create alert rule. В секции Define query and alert condition введите два запроса. Запрос A - метрика ошибок 5xx:

sum(rate({job="nginx", filename="access.log", status=~"5.."}[5m]))

Запрос B - общее число запросов:

sum(rate({job="nginx", filename="access.log"}[5m]))

В секции Expressions добавьте условие: C = A / B * 100 > 5. Это означает, что алерт сработает, когда доля ошибок 5xx превысит 5% от общего трафика за 5-минутное окно. Установите Evaluation interval в 1 минуту и Pending period в 2 минуты, чтобы избежать ложных срабатываний при кратковременных всплесках.

Настройте уведомление через Contact point. Для Telegram создайте бота через @BotFather, получите токен и ID чата, затем добавьте их в Grafana. В аннотации к алерту укажите полезную информацию: хост, текущий процент ошибок, ссылку на дашборд. Пример шаблона сообщения:

Nginx 5xx alert on {{ $labels.host }}
Error rate: {{ humanize $values.C.Value }}%
Dashboard: https://grafana.example.com/d/nginx-logs

Дополнительные сценарии для алертинга: падение общего числа запросов до нуля (Nginx или Promtail упал), рост доли 404 ошибок (возможно, удалён популярный эндпоинт или идёт сканирование уязвимостей), превышение порога среднего времени ответа. Каждый сценарий оформляется отдельным alert rule с соответствующим запросом LogQL.

Заключение: дальнейшие шаги и лучшие практики

Вы развернули стек Loki, Promtail и Grafana, настроили парсинг access.log и error.log Nginx, освоили запросы LogQL для поиска ошибок и построили дашборд с ключевыми метриками. Система готова к работе в production-среде. Для дальнейшего развития мониторинга рекомендую три шага.

Первый - настройте ротацию логов Nginx через logrotate, чтобы файлы не занимали всё дисковое пространство. Loki хранит данные независимо, но исходные файлы на сервере тоже нужно очищать. Второй - настройте долгосрочное хранение логов в объектном хранилище S3. Это снизит стоимость хранения и позволит хранить логи за месяцы для ретроспективного анализа инцидентов. Третий - интегрируйте логи с метриками Prometheus. На одном дашборде Grafana вы сможете видеть график запросов из Loki и метрики Nginx из Prometheus, сопоставляя рост трафика с увеличением времени ответа. Готовые конфиги Prometheus, Grafana и bash-скрипты для мониторинга Nginx помогут быстро настроить сбор метрик.

Если вы управляете несколькими серверами, изучите маршрутизацию логов и метрик между разными системами. Руководство по маршрутизации логов, метрик и алертов содержит готовые конфигурации для высоконагруженных сред. Для более глубокой интеграции логов в DevOps-конвейер обратитесь к материалу по построению отказоустойчивого конвейера телеметрии - он охватывает Fluentd, Prometheus Node Exporter и гибкую маршрутизацию по тегам.

Настроенный стек Loki и Promtail - это фундамент observability. С него начинается путь к проактивному управлению инцидентами: вы не ждёте жалоб пользователей, а видите проблему в момент её возникновения и устраняете до того, как она повлияет на бизнес.

Поделиться:
Сохранить гайд? В закладки браузера