Мониторинг и алертинг по логам в 2026: запросы для поиска аномалий и настройка уведомлений в Graylog, Kibana и Loki | AdminWiki

Мониторинг и алертинг по логам в 2026: запросы для поиска аномалий и настройка уведомлений в Graylog, Kibana и Loki

30 сентября 2026 14 мин. чтения
Содержание статьи

Алертинг по логам нужен там, где метрики фиксируют факт проблемы, но не объясняют её причину. Рост 5xx в Prometheus говорит, что API деградировал, а запись с level=error и текстом исключения называет эндпоинт, тип ошибки и trace_id. Логи закрывают три задачи, до которых числовые ряды не достают: отдельные исключения, ошибки бизнес-логики и подозрительные паттерны в тексте событий.

Если логи уже лежат в централизованном хранилище (Graylog, Elasticsearch, Grafana Loki), стоимость алертинга почти нулевая: тот же индекс, те же поля, добавляются правило срабатывания и канал доставки. Централизованный сбор настраивают один раз, дальше из него получают проактивные уведомления вместо архива для разбора инцидентов постфактум.

Есть три сценария, где без алертинга по логам вы слепы: всплеск 5xx на одном сервисе при стабильном общем RPS; повторяющееся исключение после деплоя, которое видно только в тексте сообщения; серия 401 или 403 с одного IP, похожая на перебор паролей. Ни один из них Prometheus в удобном виде не покажет.

Зачем превращать логи в систему алертинга, а не в архив инцидентов

Что алертинг по логам даёт поверх метрик и APM

Метрики это числа с метками, логи это события с контекстом. Счётчик http_requests_total{status="500"} скажет, что выросла доля ошибок, и не скажет, какое поле запроса пришло пустым. Разница по классам задач:

ВопросМетрики и APMЛоги
Насколько плохо сейчасLatency, RPS, доля 5xx, насыщение CPUError rate, посчитанный по записям level=error
Почему сломалосьТолько факт деградацииТип исключения, стек, параметры запроса
Кто и откудаМетки сервиса и инстансаIP, user-agent, путь, идентификатор пользователя
Единичное событиеСмазывается агрегациейОтдельная запись с полным контекстом
Ошибка бизнес-логики при HTTP 200Не виднаlevel=warn или error с текстом о неверном состоянии

Логи дополняют метрики, а не подменяют их. Держите алерт на error rate в метриках, а разбор причины автоматизируйте через логи: так дежурный получает и сигнал, и контекст в одном уведомлении.

Минимальные предпосылки: что должно быть настроено до алертов

  1. Централизованный сбор: Graylog, Elasticsearch или Loki принимают логи со всех узлов.
  2. Структурированные поля: level, service, timestamp, trace_id, host. Без поля level алертинг по логам вырождается в набор регулярных выражений, которые ломаются от любой правки формата.
  3. Ретенция: окно хранения покрывает период сравнения. Для базовой линии за 7 дней нужны логи минимум за 7 дней.
  4. Индексация по полям, по которым фильтруете: level, service, status, remote_addr.
  5. Канал доставки: SMTP для почты, webhook для Slack и Telegram.

Пример структурированной записи, на которую удобно строить запросы:

{
  "timestamp": "2026-09-30T21:00:00Z",
  "level": "error",
  "service": "api",
  "host": "api-03",
  "trace_id": "8f3c1a2e",
  "status": 500,
  "message": "Order <uuid> failed after <n> retries"
}

Если приложение пишет неструктурированный текст в stdout, начните с перевода на JSON. Сделать это проще на уровне библиотеки логирования, чем потом разбирать регулярками десятки форматов сообщений.

Как писать запросы для поиска аномалий и всплесков ошибок

Любой алерт по логам состоит из трёх частей: выборка событий, агрегация по окну и условие. Частая ошибка - писать условие по сырым событиям, например count больше нуля по слову error. Такое правило сработает на первом безобидном сообщении. Разберём три группы запросов: всплески, повторяющиеся исключения и подозрительная активность. Шаблоны под EQL и LogQL с интеграцией в Alertmanager собраны в руководстве по алертингу на основе логов для Elasticsearch и Grafana Loki.

Запросы для всплесков ошибок: rate, count_over_time и группировка

В Loki частоту ошибок считают через rate по потоку:

sum(rate({app="api"} |= "level=error" [5m])) by (service)

В Kibana тот же смысл даёт фильтр level:error и агрегация date_histogram с интервалом 1 минута, а в Graylog - поиск level:ERROR с агрегацией count по времени. Окно 5 минут выбрано осознанно: минутное окно даёт рваный график и ложные срабатывания на единичных всплесках, а пять минут сглаживают шум и остаются достаточно быстрыми для реакции дежурного.

Чтобы учесть суточную сезонность, сравнивайте текущее окно с тем же окном сутки назад, а не с предыдущими пятью минутами:

sum(rate({app="api"} |= "level=error" [5m])) by (service)
  /
sum(rate({app="api"} |= "level=error" [5m] offset 1d)) by (service)

Группировка by (service) обязательна. Без неё правило считает ошибки по всему кластеру: один упавший сервис из двадцати не перебьёт общий фон, и алерт промолчит. Разбивайте по сервису, а внутри сервиса - по эндпоинту или коду ответа, если трафик большой.

Доля ошибок считается как отношение двух потоков:

sum(rate({app="api"} |= "level=error" [5m]))
  /
sum(rate({app="api"} [5m]))

Порог для доли удобнее абсолютного числа: 5% ошибок от запросов означает одно и то же и при 100 RPS, и при 10 000 RPS.

Поиск повторяющихся исключений и топ-N ошибок

Исключения тонут в потоке, если смотреть на них поштучно. Вытаскивайте топ за окно:

topk(10,
  sum by (exception_type) (
    count_over_time({app="api"} |= "Exception" [1h])
  )
)

Если поля exception_type в логах нет, группируйте по нормализованному тексту сообщения. Нормализация нужна потому, что в строку попадают переменные, и каждое сообщение становится уникальным:

was: Order 8f3c1a2e-7b21 failed after 12 retries
now: Order <uuid> failed after <n> retries

В Elastic-стеке похожие сообщения схлопывают через дедупликацию по отпечатку: fingerprint-фильтр Logstash создаёт хеш из одного или нескольких полей (по умолчанию message), и этот хеш используется как document ID, чтобы Elasticsearch обновлял существующий документ вместо создания дубликата. Альтернативный приём - генерация уникального ID хешированием набора полей события, например fields: ["event.start_date", "event.data_set", "message"]: при совпадении исходный документ обновляется при индексации. В Graylog группировка событий задаётся через поля события в Event Definition.

Запросы для подозрительной активности: 401/403, 404, аномальные объёмы

Серия неуспешных авторизаций с одного адреса:

sum(count_over_time({app="nginx"} |= " 401 " [5m])) by (remote_addr)

Условие правила: результат больше 20 за окно. Порог подбирайте по своей базовой линии, а не переносите из чужой статьи: для API с мобильными клиентами 20 ошибок авторизации за пять минут с одного IP нормальны при массовом перелогине пользователей.

Всплеск 404 на одном пути:

topk(5,
  sum by (path) (count_over_time({app="nginx"} |= " 404 " [10m]))
)

Алертить на каждый 404 бессмысленно: сканеры и боты постоянно перебирают пути. Смотрите на всплеск на одном конкретном пути и на рост числа уникальных путей с одного адреса.

Аномальный объём ответов в байтах:

sum by (remote_addr) (
  sum_over_time({app="nginx"} | logfmt | unwrap body_bytes_sent [10m])
)

Пример требует, чтобы nginx писал логи в logfmt или JSON с полем body_bytes_sent. Иначе распарсите нужное поле через pattern или regexp и только потом суммируйте.

Пороговые правила против аномалий по базовой линии: что выбрать

Пороговое правило читается одной строкой: если ошибок больше 100 за 5 минут, шли уведомление. Плюс в простоте и предсказуемости, минус в том, что порог не знает про сезонность и рост трафика. Базовая линия сравнивает текущее значение со статистикой прошлых периодов: если ошибок больше, чем среднее за 7 дней плюс 3 стандартных отклонения, шли уведомление. Такое правило адаптируется само, но требует истории и хуже объясняется дежурному.

КритерийПороговое правилоБазовая линия
НастройкаОдна константаНужна история и выбор окна
СезонностьНе учитываетУчитывает при сравнении с тем же часом прошлых дней
Рост трафикаПорог пересматривают вручнуюПодстраивается автоматически
Слабые местаС запасом пропускает инциденты, без запаса шумитПри малом числе событий stddev нестабилен
Объяснение дежурномуПонятно сразуНужен график и значение baseline
Когда выбиратьСтабильный трафик, редкие критичные событияСезонный или растущий трафик, плавающая норма

Как построить простую базовую линию без ML

Формулы, которой хватает в большинстве случаев:

baseline = avg_over_time(error_rate[7d]) + 3 * stddev_over_time(error_rate[7d])

Здесь error_rate - записанная метрика, то есть recording rule, посчитанный по логам. Loki готового отношения не отдаёт, поэтому либо считайте error rate регулярно и складывайте в Prometheus, либо переносите расчёт в LogQL-метрики и сравнивайте окна через offset.

Z-score даёт то же в явном виде:

z = (current_value - baseline_mean) / baseline_stddev

Правило срабатывает при z больше 3. Для суточной сезонности сравнивайте с тем же часом предыдущих дней, а не со средним за неделю: ночной минимум и вечерний пик дают разные нормы, и общее среднее сместит baseline. Практическое ограничение: при малом числе событий стандартное отклонение нестабильно, поэтому берите окно, где набирается хотя бы сотня событий. На старте базовая линия требует 1-2 недели данных, иначе первые дни будут ложные срабатывания.

Когда порог лучше базовой линии

Если событие в норме не должно происходить вообще, ставьте порог. Потеря подключения к БД, срабатывание OOM-killer, ошибка миграции: любое срабатывание важно, статистика тут не нужна. Редкие события тоже плохо ложатся на базовую линию: чтобы посчитать stddev, нужны данные, а их нет. Простое правило выбора: событие в норме не происходит - порог, событие в норме происходит с разной частотой - базовая линия.

Настройка алертов в Graylog, Kibana и Grafana Loki

Ниже путь для трёх стеков. Названия пунктов меню отличаются между релизами, сверяйтесь со своей версией: точные пути и номера версий в открытой документации приведены не для всех релизов, поэтому ориентируйтесь на свою установку. Общая логика одинакова: запрос, условие, группировка, канал доставки.

Graylog: Event Definitions и уведомления

  1. Создайте Event Definition: в документации Graylog алерты настраиваются через определение условий события, триггеров и уведомлений.
  2. Задайте Condition. Filter выбирает события (например, level:ERROR), Aggregation задаёт порог: count больше 100 за 5 минут.
  3. Заполните поля для группировки: service, host. Эти поля попадут в уведомление и разделят события на независимые алерты.
  4. Добавьте Notification: Email для почты или HTTP для webhook в Slack.

Пример payload для webhook в Slack:

{
  "text": "5xx spike: ${event.source}",
  "fields": {"service": "${event.fields.service}"}
}

Graylog не отправит ни одного уведомления, пока у события не настроен Notification. Проверьте, что SMTP прописан в конфигурации сервера, иначе рассылка по почте молча не работает.

Kibana: Rules and Connectors

  1. Stack Management, затем Rules and Connectors, затем Create rule.
  2. Выберите тип Log threshold.
  3. Задайте KQL-запрос, например level:error and service:api, и порог: count больше 100 за 5 минут.
  4. Создайте Connector: Slack (нужен Incoming Webhook) или Email (нужен SMTP в kibana.yml).
  5. Привяжите Connector к правилу и сохраните.

Проверить правило можно кнопкой Test: Kibana отправит тестовое уведомление через выбранный Connector, не дожидаясь реального срабатывания. Для правил по логам индексация должна покрывать всё окно проверки, иначе count будет неполным.

Grafana Loki: Alert rules и Contact points

  1. Alerting, затем Contact points, затем New contact point. Для Telegram понадобятся токен бота и chat_id.
  2. Notification policies: маршрутизация по метке severity, например critical в Telegram, warning в Slack.
  3. Alert rules, затем New alert rule: выбираете Loki как источник данных и вставляете LogQL-запрос.
  4. Условие: sum(rate(...)) by (service) больше 0.05, окно 5 минут, параметр for 5m.
  5. Проверьте кнопкой Test и посмотрите состояние в Alerting, затем Active alerts.

Пример правила на всплеск ошибок:

sum(rate({app="api"} |= "level=error" [5m])) by (service) > 0.05

Параметр for задаёт, сколько правило должно оставаться в состоянии срабатывания до отправки: без него уведомления будут приходить на каждый короткий всплеск. Шаблоны сообщений, функции reduce и math и работу с метками разбираем в руководстве по продвинутым алертам в Grafana.

Как снизить ложные срабатывания через группировку событий

Тысяча одинаковых ошибок за минуту это одно событие, а не тысяча уведомлений. Отсутствие группировки превращает любой канал в спам и приводит к алертной усталости быстрее, чем неправильно выбранные пороги.

Группировка и дедупликация: одно уведомление вместо тысячи

В Grafana Alerting опция group_by задаёт критерии группировки входящих алертов внутри notification policy, а таймеры (group wait, group interval, repeat interval) определяют, когда и как часто отправляется уведомление. По умолчанию политики уведомлений группируют алерты по правилу алерта, используя метки alertname и grafana_folder, поскольку имена правил не уникальны между папками. Пример настройки группировки по сервису:

group_by: [alertname, service]

В Kibana то же делается через group by field со значением service, в Graylog через поля события в Event Definition. Дедупликация работает по ключу: события с одинаковым набором меток схлопываются в одно уведомление, а не отправляются поштучно. Дополнительно ограничьте частоту: throttle не чаще одного уведомления раз в 5 минут по одной группе.

Отдельная проблема - flapping, когда алерт то срабатывает, то гаснет. Окно for: 5m лечит его: правило отправляет уведомление, только если условие держится всё окно целиком.

Для маршрутизации между Prometheus, Grafana, ELK и Loki под нагрузкой есть разбор схем маршрутизации системных событий и логов.

Подавление на время деплоя и плановых работ

Во время деплоя часть ошибок ожидаема. Ставьте silence заранее и автоматически из CI/CD, а не руками. Silences API в Alertmanager входит в v2 API и позволяет создавать, читать, обновлять и удалять silences, которые подавляют уведомления для совпадающих алертов. Точный путь эндпоинта зависит от вашей инсталляции Grafana, поэтому сверяйтесь с документацией своей версии:

curl -X POST -H "Content-Type: application/json" --data @silence.json \
  $GRAFANA_URL/api/alertmanager/grafana/api/v2/silences

В Grafana для регулярных окон есть mute timings: например, подавление предупреждений на время nightly-бэкапа. Provisioning HTTP API Grafana Alerting поддерживает получение одного mute timing, получение всех mute timings и создание нового mute timing. В Kibana аналогичную роль играют maintenance windows. Пример silence на 30 минут при деплое:

{
  "matchers": [{"name": "service", "value": "api", "isRegex": false}],
  "startsAt": "2026-09-30T21:00:00Z",
  "endsAt": "2026-09-30T21:30:00Z",
  "createdBy": "ci",
  "comment": "deploy api v1.42.0"
}

Silence по сервису и метке деплоя точнее глобального отключения алертов: остальные сервисы продолжают будить дежурного. Готовые правила подавления и группировки для Alertmanager собраны в практическом руководстве по Prometheus и Alertmanager.

Куда отправлять уведомления: почта, Slack, Telegram

КаналДля чего подходитОграничение
Email (SMTP)Аудит, несрочные алерты, отчётыМедленно, легко теряется в ящике
Slack (Incoming Webhook)Командная работа, разбор в рабочее времяОколо одного сообщения в секунду на webhook
Telegram (Bot API)Дежурные, мобильные уведомленияНужен chat_id, нет тематических потоков

Маршрутизация по важности: critical идёт в Telegram дежурному, warning в командный Slack, info уходит в почту и никого не будит. Общий чат на все алерты убивает канал за неделю: сообщения про 404 от сканеров смешаются с падением продакшена.

Slack: Incoming Webhook и форматирование сообщений

Создайте Incoming Webhook в настройках приложения Slack и вставьте его в Connector или в Notification. Payload с цветом по важности:

{
  "text": "API: 5xx above threshold",
  "attachments": [
    {"color": "danger", "title": "service=api", "text": "120 errors / 5m"}
  ]
}

Цвет danger для critical, warning для предупреждений: дежурный оценивает важность без чтения текста. Учитывайте ограничение webhook: Slack допускает не более одного сообщения в секунду на канал, в том числе через incoming webhook, и отдельно указывает лимит 1 сообщение в секунду на webhook URL. Короткие всплески допускаются, но при продолжительном превышении лимита Slack начинает применять rate limiting и возвращает HTTP 429 Too Many Requests с заголовком Retry-After, содержащим число секунд до повторной попытки. Группировка из предыдущего раздела снижает риск упереться в этот лимит.

Telegram: Bot API и chat_id

  1. Создайте бота через @BotFather и получите токен.
  2. Напишите боту любое сообщение от своего аккаунта.
  3. Запросите обновления методом getUpdates и возьмите chat_id из ответа.
  4. Укажите токен и chat_id в contact point Grafana.

Проверка отправки:

curl -s "$TELEGRAM_API/bot$TELEGRAM_TOKEN/sendMessage" \
  -d chat_id=$TELEGRAM_CHAT_ID \
  -d text="test alert: api 5xx"

Токен держите в секретах Grafana или в переменных окружения, не в тексте правила: утечка токена отдаёт постороннему возможность писать в ваш рабочий чат. Для нескольких дежурных используйте отдельные contact points и notification policy с привязкой к расписанию on-call.

Проверка и поддержка правил: чек-лист перед продом

Правило, которое не сработало в момент аварии, хуже отсутствующего: на него рассчитывают. Проверяйте алерты до прода.

Как протестировать алерт без реального инцидента

Сгенерируйте событие, которое попадёт в индекс и подойдёт под фильтр:

logger -p user.error "TEST ALERT api 5xx monitor check"

Что проверить после отправки: событие попало в поток с нужным полем level; правило перешло в firing в течение окна for; уведомление дошло до канала. В Grafana перед сохранением используйте Test rule, в Kibana кнопку Test у правила. Тестовые записи помечайте словом TEST, чтобы они не искажали последующие запросы по базовой линии.

Ревизия правил и борьба с алерт-фатигом

Алертная усталость наступает, когда уведомлений больше, чем дежурный способен обработать: он перестаёт открывать алерты, отключает правила, переносит их в отдельный канал. Признак проблемы простой: если правило срабатывает чаще раза в день и не требует действия, его удаляют или понижают severity. Ревизия раз в квартал, удаление мёртвых правил и снижение шума через группировку возвращают доверие к системе. Приёмы подавления шума и эскалации разбирали в статье про умные уведомления и борьбу с шумом.

Второй ориентир для ревизии - SLO: алерт нужен там, где расходуется error budget. Если срабатывание не связано с SLO сервиса и не требует действий, оно занимает чужое внимание.

Типичные ошибки при алертинге по логам

  • Алерт на каждый ERROR. Днём это сотни уведомлений. Сравнивайте с нормой: долю ошибок от запросов или всплеск относительно базовой линии.
  • Правило без владельца. Непонятно, чей это сервис и к кому идти. Указывайте владельца в метке и в описании правила.
  • Правило без runbook. Дежурный ночью не знает, что делать. Добавьте ссылку на инструкцию прямо в текст уведомления.
  • Порог без учёта сезонности. Ночной трафик в разы ниже дневного, фиксированный порог будет то молчать, то срабатывать на нормальном пике.
  • Все алерты в один общий чат. Канал превращается в ленту, критичное тонет в потоке.
  • Нет теста правила. Алерт не срабатывает в аварию, потому что фильтр не совпал с реальным форматом записи.
  • Отсутствие группировки. Тысяча событий даёт тысячу уведомлений и блокировку канала.

Перед включением правила в прод ответьте на пять вопросов: что именно оно ловит, при каком условии срабатывает, кому приходит, что делать дежурному и как проверить срабатывание без аварии. Если ответа нет хотя бы на один, правило рано включать.

Поделиться: