Vector для журналов Directum RX: что разбирать на месте и девять граблей, на которых теряются записи
Как собрать журналы Directum RX, PostgreSQL, nginx и аудита через Vector так, чтобы по ним можно было разбирать инциденты. Поля журнала RX по документации вендора, склейка записей PostgreSQL, единое время и девять ошибок, из-за которых записи пропадают молча.
Коротко. Журналы RX структурированы, но поля mt, args и cust меняют тип, и Elasticsearch молча отвергает такие записи. PostgreSQL пишет ошибку несколькими строками, и склейка по дате их разрывает. Свежий Vector не подставляет переменные окружения в конфигурацию. Буфер на диске, явный часовой пояс, право create с
action: createи тревога на ошибки отправки закрывают большую часть потерь.
Сборщик журналов настраивают один раз, проверяют глазами, что записи пошли, и забывают. Через месяц разбирают инцидент и выясняется, что ошибки PostgreSQL лежат без текста запроса, у половины записей RX время в другом часовом поясе, а за ночь аварии в хранилище нет ничего: сборщик всю ночь получал отказ и выбрасывал записи. Ниже разбор, как собрать журналы RX и окружения через Vector, и девять мест, где записи теряются без единой ошибки на экране. Все девять мы поймали, когда проверяли конфигурацию настоящими программами в контейнерах.
Симптом
Журналы вроде бы собираются, панели рисуются. Но при разборе конкретного случая:
- ошибка PostgreSQL есть, а запроса, который её вызвал, нет, он лежит отдельной записью без связи с ней;
- записи с разных серверов не выстраиваются по времени, разница ровно три часа;
- в поле
hostу части записей стоит не имя сервера, а адрес сайта или случайный набор символов; - в хранилище дыра за период, когда оно было недоступно или перегружено;
- часть записей RX не доходит вовсе, а в журнале сборщика ошибка про тип поля.
Почему так
Сборщик между источником и хранилищем делает три вещи: читает строки, превращает их в поля и отправляет. Каждый шаг ломается по-своему.
Чтение. Многие журналы многострочные. Запись PostgreSQL об ошибке занимает несколько строк: сама ошибка, DETAIL,
STATEMENT, иногда HINT и CONTEXT. Если сборщик считает каждую строку отдельной записью, связь между ошибкой
и запросом теряется.
Разбор. Каждый источник пишет время по-своему. Журнал RX пишет время с часовым поясом, nginx в журнале ошибок
без пояса, PostgreSQL сокращением вроде MSK, которое однозначно не разобрать. Журнал RX сам по себе структурирован,
это JSON, но некоторые его поля бывают то строкой, то объектом.
Отправка. Хранилище бывает недоступно, отвечает отказом в правах или отвергает запись из-за конфликта типов. Если сборщик не хранит события на диске до успешной отправки, они пропадают.
Что в журнале RX
Сервисы RX на Linux пишут журналы строками JSON в каталог из параметра LOGS_PATH в config.yml Directum Launcher,
по умолчанию это <папка с данными>/logs. Журналы веб-клиента и веб-агента лежат в подпапке remote. Имя файла
содержит имя сервиса: <компьютер>-<контейнер>.WebServer.<дата>.log, ...Worker..., ...IndexingService....
Состав полей описан в документации вендора, раздел «Структура лог-файлов»:
| Поле | Что в нём |
|---|---|
t |
время события с часовым поясом |
l |
уровень: Fatal, Error, Warning, Info, Debug, Trace |
mt |
текст сообщения: строка или объект |
args, cust |
параметры сообщения и дополнительные сведения |
ex |
исключение: type, m, stack |
span |
операция и её длительность |
lg, tr, un, v |
логгер, трассировка, учётная запись, версия |
Что стоит вынести наверх при разборе: сервис из имени файла, уровень, текст, логгер, пользователя, тип исключения,
стек и метод, в котором оно случилось (первая строка at ...( стека), операцию и её длительность. Остальное
можно хранить целиком в отдельном поле.
Отдельно полезно поле с общим видом сообщения. Это тот же текст, где адреса, пути, GUID, числа и строки в кавычках заменены заглушками: «Document 18234 not found» и «Document 90211 not found» превращаются в одну строку «Document {N} not found». По ней удобно считать, какая ошибка случается чаще всего.
Девять граблей
Каждую из них мы получили на проверке собранной конфигурации в контейнерах с Vector 0.58, Elasticsearch 8.19 и Loki 3.7.
1. Пароль из переменной окружения не подставляется. Во множестве инструкций пароль к хранилищу пишут
в конфигурацию как ${ES_PASSWORD}. В свежих версиях Vector подстановка переменных окружения выключена
по умолчанию: в хранилище уходит буквальная строка с долларом, в ответ приходит 401. Включается она флагом
с говорящим названием --dangerously-allow-env-var-interpolation. Правильнее встроенное хранилище секретов
Vector: каталог, где каждый пароль лежит отдельным файлом, а в конфигурации ссылка вида SECRET[files.es_password].
2. Право только на создание документов, а Vector пишет действием index. Пользователю сборщика логично выдать
минимальные права: создавать свои индексы и добавлять в них документы (create_doc). Vector по умолчанию
отправляет записи действием index, и Elasticsearch отвечает security_exception. Для журналов, которые только
дописываются, в настройках приёмника ставится action: create.
3. Поле host затирается. Журнал ошибок nginx содержит поле host, это заголовок запроса. Если результат
разбора слить с записью целиком, имя сервера в host заменится адресом сайта. Разобранное кладут в отдельный
объект nginx.*.
4. Имя сервера в контейнере случайное. Vector в контейнере ставит в host имя контейнера, а оно случайное
и меняется при пересоздании. В команде запуска имя хоста передаётся явно: --hostname "$(hostname)".
5. PostgreSQL: запрос отдельно от ошибки. Склейка «новая запись начинается с даты» не работает: строки DETAIL
и STATEMENT тоже начинаются с даты. Новой записью считается только строка с префиксом и уровнем LOG, ERROR,
WARNING, FATAL, PANIC, а DETAIL, STATEMENT, HINT, CONTEXT приклеиваются к предыдущей. После этого
из склеенной записи достают текст запроса и подробности в отдельные поля.
6. Часовой пояс сокращением. PostgreSQL по умолчанию пишет 2026-10-11 21:15:05.345 MSK. Сокращение
однозначно не разбирается, поэтому пояс для таких записей задаётся в конфигурации явно. Если в PostgreSQL выставлен
log_timezone = 'UTC', этот шаг не нужен. Та же история с журналом ошибок nginx, где пояса нет совсем.
7. Конфликт типов в Elasticsearch. В журнале RX поле mt у одних записей строка, у других объект. Поля args
и cust у разных сообщений содержат значения разных типов. Elasticsearch закрепляет тип поля по первой записи
и отвергает все следующие с другим типом. Отвергнутые записи пропадают, а заметно это только в журнале самого
сборщика. Такие поля сохраняют строкой JSON.
8. Отсутствующее поле превращается в ноль. При разборе журнала аудита числовые поля переводят в числа.
Функция преобразования в VRL превращает отсутствующее значение в 0, и у записи без euid появляется euid=0.
В журнале аудита 0 означает root: запись начинает выглядеть как действие суперпользователя. Числа переводят только
из тех полей, что в записи реально есть.
9. Сборщик собирает сам себя. Если Vector читает системный журнал, в нём есть и записи самого Vector. Когда хранилище отвечает ошибками, Vector пишет об этом в журнал, читает эти записи и пытается отправить их в то же хранилище. Петля включается ровно тогда, когда хранилищу и так плохо. Служба Vector исключается из сбора.
Ещё две вещи не ломают сбор сразу, но всплывают позже. Веб-агент RX пишет пути Windows с одиночным обратным
слешем (C:\Program Files), и строгий разбор JSON на такой строке падает. Лишний слеш удваивают и разбирают строку снова.
Команды sudo в журнале аудита записаны шестнадцатеричной строкой, и без перевода в текст по ним не найти,
кто перезапускал службу.
Диагностика
Проверить, что сборщик ничего не теряет, можно по его собственным метрикам. Vector отдаёт их для Prometheus:
curl -s localhost:9598/metrics | grep -E 'component_errors_total|component_discarded_events_total|buffer_byte_size' | grep -v ' 0$'
| Метрика | Что значит рост |
|---|---|
vector_component_errors_total у приёмника |
хранилище отвечает ошибкой: права, конфликт типов, недоступность |
vector_component_discarded_events_total |
события выброшены |
vector_buffer_byte_size |
хранилище не успевает или недоступно, события копятся на диске |
Посмотреть, на какие поля разложилась запись, можно до отправки: vector tap --outputs-of <имя разбора>.
Что сделать
- Включить буфер на диске у приёмника: при недоступности хранилища события ждут, а не пропадают.
- Склеивать многострочные записи по правилам источника: у PostgreSQL новая запись начинается с префикса и уровня.
- Приводить время к UTC при разборе, задавая пояс там, где источник его не пишет.
- Сохранять строкой поля, тип которых меняется от записи к записи.
- Писать по одному индексу на семейство источников в сутки, ошибки RX отдельно: их хранят дольше и ищут чаще. Почему не по индексу на сервис, разобрано в статье о журналах RX.
- Поставить тревогу на ошибки отправки и заполнение буфера сборщика.
Конфигурацию со всем перечисленным собирает генератор конфигурации Vector. Срок хранения и число шардов под ваш диск считает калькулятор Elasticsearch.
Чего не делать
- Не проверять сбор глазами. «Записи пошли» не значит «пошли все». Смотреть на метрики ошибок и выброшенных событий.
- Не держать пароль хранилища в конфигурации. Её копируют, коммитят и пересылают.
- Не собирать отладочный уровень постоянно. Он увеличивает объём в разы и нужен на время разбора.
- Не класть в метки Loki пользователя или идентификатор запроса. Каждое значение создаёт отдельный поток, и Loki начинает отказывать в приёме.
Похожая картина у вас?
Разберём ваш контур за 30 минут бесплатного созвона: версия RX, состав сервисов, что болит сильнее всего.
Написать