Обогащение событий: mutate, date, geoip, drop

Набор фильтров, которые доводят разобранное событие до пригодного для аналитики вида.

Обогащение — добавление к событию новых полезных полей и приведение существующих к правильному виду уже после первичного разбора строки.

Зачем обогащать

grok извлёк поля — но этого мало. Время в логе записано строкой, а нужен настоящий @timestamp. По IP хочется видеть страну на карте. Лишние технические поля надо выкинуть, имена — привести к единому стилю. Этим занимается набор фильтров обогащения.

date: правильное время события

Самый важный фильтр. По умолчанию @timestamp события — это момент, когда Logstash обработал строку, а не когда событие произошло. Если логи пришли с задержкой или переотправились, время поедет. Фильтр date берёт извлечённое grok'ом текстовое поле времени и кладёт его в @timestamp:

filter {
  date {
    match => [ "timestamp", "dd/MMM/yyyy:HH:mm:ss Z" ]
    target => "@timestamp"
  }
}

Теперь событие на временной шкале Kibana стоит там, где реально случилось.

mutate: правка полей

Универсальный фильтр-редактор. Умеет переименовывать, удалять, преобразовывать тип, приводить регистр, обрезать пробелы:

filter {
  mutate {
    rename => { "client_ip" => "source.ip" }
    convert => { "bytes" => "integer" }
    remove_field => [ "timestamp", "http_version" ]
    uppercase => [ "method" ]
  }
}

Здесь мы переименовали поле под стандарт ECS, привели bytes к числу, удалили уже ненужное текстовое timestamp (его значение мы перенесли в @timestamp) и привели метод к верхнему регистру.

geoip: география по IP

Фильтр geoip по IP-адресу добавляет страну, город и координаты из встроенной базы MaxMind. Это превращает скучный список IP в карту запросов в Kibana:

filter {
  geoip {
    source => "source.ip"
    target => "geo"
  }
}

В событие добавятся geo.country_name, geo.city_name, geo.location (координаты для карты). Сразу видно, откуда идёт трафик и не льётся ли подозрительный поток из одной страны.

drop: отсев шума

Не всё стоит хранить. Шумные DEBUG-логи или health-check'и (GET /health каждую секунду) раздувают индекс и кошелёк. Фильтр drop выбрасывает событие по условию ещё до индексации:

filter {
  if [path] == "/health" or [level] == "DEBUG" {
    drop { }
  }
}

Это один из самых дешёвых способов сократить объём логов: не собирать то, что не нужно.

Как работает под капотом: всё это просто мутация хеша

Событие внутри Logstash — это структура (по сути словарь полей). Каждый фильтр получает её, что-то меняет и передаёт дальше. geoip лезет в локальную базу MaxMind (никаких внешних запросов на каждое событие — иначе было бы катастрофически медленно), date парсит строку в timestamp, mutate правит ключи. Поскольку фильтры идут по цепочке, важно ставить drop как можно раньше: нет смысла тратить geoip и grok на событие, которое всё равно выбросим.

Порядок и стоимость обогащения

Фильтры обогащения сильно различаются по стоимости, и это влияет на их порядок. mutate и date дёшевы — это операции в памяти над уже распарсенным событием. geoip дороже, потому что обращается к базе данных (пусть и локальной) на каждое событие. Поэтому общая стратегия такая: сначала максимально дёшево отсеять ненужное (drop в самом начале), затем разобрать (grok или json), привести время (date), почистить поля (mutate), и только в конце применять дорогие обогащения вроде geoip к тем событиям, которые точно сохранятся. Каждое дорогое обогащение, применённое к событию, которое потом выбросят, — это зря потраченные ресурсы, умноженные на объём потока.

Отдельно стоит сказать про обогащение из внешних источников. Помимо geoip, в Logstash есть фильтры вроде translate (подмена значения по словарю — например, код ошибки в человекочитаемое описание) и elasticsearch (дотянуть данные из другого индекса). Они мощные, но каждое внешнее обращение замедляет конвейер, поэтому такие обогащения кэшируют и применяют осмотрительно. Идея обогащения в том, чтобы сделать лог самодостаточным для чтения в момент записи, а не заставлять читателя потом гадать, что означает загадочный код, — но платить за это разумную цену.

Частые ошибки

  • Не настроить date. Без date все события встают на временной шкале по времени обработки — анализ инцидента «что было в 14:32» становится бессмысленным.
  • drop в конце цепочки. Если отбрасывать шум после geoip и grok, вы зря тратите ресурсы на обработку выбрасываемых событий. Ставьте drop в начало.
  • geoip на приватные IP. Для 10.x/192.168.x geoip ничего не найдёт и создаст пустые поля. Применяйте geoip только к публичным адресам.

Итоги

  • date переносит реальное время события в @timestamp — без него временная шкала врёт.
  • mutate переименовывает, удаляет и приводит типы полей; geoip добавляет географию по IP.
  • drop отсеивает шум (DEBUG, health-check) до индексации и экономит объём — ставьте его раньше grok/geoip.
  • Событие — это структура полей; фильтры по цепочке её мутируют.
Проверьте себя
1. Что делает фильтр date в Logstash и почему он важен?
AУдаляет старые события
BПереносит реальное время события из текстового поля в @timestamp, иначе на шкале стоит время обработки, а не события
CДобавляет текущую дату ко всем логам
DФорматирует дату для красоты
2. Зачем ставить фильтр drop как можно раньше в цепочке фильтров?
AИначе drop не сработает
BЧтобы не тратить ресурсы на grok и geoip для событий, которые всё равно будут выброшены
Cdrop работает только первым по техническим причинам
DЧтобы ускорить Elasticsearch
3. Что добавляет фильтр geoip к событию?
AСлучайные координаты
BСтрану, город и координаты по IP-адресу из локальной базы MaxMind
CИмя пользователя по IP
DСкорость соединения