Graylog — Документация по развёртыванию и настройке
Оглавление
- Архитектура Graylog
- Компоненты системы
- Ключевые концепции
- Потоки данных — от источника до индекса
- Источник 1: MikroTik (Syslog)
- Источник 2: Серверные логи (Vector → GELF)
- Pipeline Rules — синтаксис и примеры
- Index Sets — управление хранением
- Рекомендации по масштабированию
Архитектура Graylog
Компоненты системы
Graylog Server
Основной сервер обработки логов. Принимает сообщения через Inputs, обрабатывает их через Pipelines, маршрутизирует в Streams и записывает в OpenSearch. Предоставляет веб-интерфейс для поиска, дашбордов и управления.
OpenSearch
Поисковый движок и хранилище логов. Все сообщения индексируются и хранятся здесь. Graylog управляет индексами через Index Sets — создаёт новые, ротирует и удаляет старые автоматически. OpenSearch обеспечивает полнотекстовый поиск и агрегации для дашбордов.
MongoDB
Хранит только метаданные Graylog: конфигурацию системы, определения pipeline rules, настройки стримов, дашборды, учётные записи пользователей. Логи в MongoDB не хранятся.
Ключевые концепции
Input
Точка входа для логов в Graylog. Определяет протокол и порт приёма. Примеры: Syslog UDP на порту 5140, GELF UDP на порту 12201. Input сам по себе не фильтрует и не маршрутизирует — он просто принимает данные и помещает их в Default Stream.
Default Stream
Встроенный стрим "All messages", куда попадают все входящие сообщения без исключения. Является точкой входа для pipeline-обработки. Все pipelines для маршрутизации подключаются именно сюда. Имеет свой Default Index Set, куда пишутся сообщения, не направленные ни в один другой стрим.
Stream
Именованный подмножество логов, отфильтрованный по определённым критериям. Каждый стрим привязан к своему Index Set, что определяет как долго хранятся его данные. Стримы также используются для разграничения доступа, алертов и дашбордов.
Pipeline
Набор правил (rules), подключённый к одному или нескольким стримам. Выполняет две основные задачи:
- Маршрутизация — направление сообщений из Default Stream в целевые стримы через
route_to_stream() - Обогащение — извлечение и добавление полей, трансформация данных
Pipeline состоит из стейджей (stages), которые выполняются последовательно (Stage 0 → Stage 1 → ...). Правила внутри одного стейджа выполняются параллельно.
Важно: Stream rules — это legacy-функция. Graylog рекомендует использовать Pipeline rules для маршрутизации.
Index Set
Конфигурация хранения группы логов в OpenSearch. Определяет:
- Rotation — когда создавать новый индекс (по времени, размеру или количеству документов)
- Retention — сколько хранить данные перед удалением
- Физические параметры — количество шардов, реплик
Разные Index Sets позволяют хранить разные типы логов с разными сроками и оптимизировать поиск (при запросе к стриму OpenSearch просматривает только его индексы).
Потоки данных — от источника до индекса
Общая схема обработки сообщения
1. Источник отправляет лог → Input принимает
2. Сообщение попадает в Default Stream
3. Pipelines на Default Stream обрабатывают сообщение:
a. Stage 0: извлечение полей, обогащение, дефолтные значения
b. Stage 1+: классификация, маршрутизация через route_to_stream()
4. Сообщение попадает в целевой Stream
5. Pipelines на целевом Stream (если есть) дополнительно обрабатывают
6. Сообщение записывается в Index Set, привязанный к стриму
7. OpenSearch индексирует и хранит сообщение
При использовании route_to_stream(name: "...", remove_from_default: true) сообщение удаляется из Default Stream и не дублируется в Default Index Set.
Источник 1: MikroTik (Syslog)
Настройка на стороне MikroTik (ROS7)
/system/identity/set name=ccr-office
/system/logging/action/add name=graylog target=remote remote=172.20.109.253 remote-port=5140 bsd-syslog=yes
/system/logging/add action=graylog topics=info
/system/logging/add action=graylog topics=warning
/system/logging/add action=graylog topics=error
/system/logging/add action=graylog topics=critical
Input в Graylog
Тип: Syslog UDP, порт: 5140, Bind address: 0.0.0.0.
Как выглядит сырое сообщение
Из MikroTik в формате BSD syslog приходит строка вида:
ccr-office <l2tp-KAR_Kungrad>: connected
или
ccr-office login failure for user admin from 38.180.71.32 via winbox
Формат: <hostname> <текст сообщения>. MikroTik в BSD syslog режиме не передаёт topic (system, firewall, l2tp) как отдельное структурированное поле — topic либо входит в текст сообщения, либо теряется.
Обработанное сообщение в Graylog
{
"timestamp": "2026-04-13T11:57:21.000Z",
"source": "ccr-office",
"facility": "syslogd",
"facility_num": 5,
"level": 6,
"message": "ccr-office <l2tp-KAR_Kungrad>: connected",
"mt_service": "l2tp",
"mt_detail": "<l2tp-KAR_Kungrad>: connected",
"streams": ["69d7a9b3b759cc2c88a9cff8"],
"gl2_source_input": "69d7a69bb759cc2c88a9bfc0",
"gl2_remote_ip": "10.21.10.1"
}
Поля mt_service и mt_detail добавляются pipeline-правилами (см. раздел Pipeline Rules).
Pipeline: Mikrotik (подключён к стриму Mikrotik)
Stage 0 — Извлечение полей:
rule "MT detail extract"
when
has_field("message")
then
let msg = to_string($message.message);
let content = grok("%{WORD} %{GREEDYDATA:content}", msg);
set_field("mt_detail", to_string(content["content"]));
set_field("mt_service", "other");
end
Логика: первое слово в message — hostname (дублирует source), всё после него — детали сообщения. По умолчанию mt_service ставится "other".
Stage 1 — Классификация по ключевым словам:
Каждый сервис — отдельное правило. Все правила в Stage 1 выполняются параллельно и перезаписывают mt_service если совпали.
rule "MT service L2TP"
when
contains(to_string($message.message), "l2tp", true)
then
set_field("mt_service", "l2tp");
end
rule "MT service wireguard"
when
contains(to_string($message.message), "wireguard", true)
then
set_field("mt_service", "wireguard");
end
rule "MT service account"
when
contains(to_string($message.message), "logged in", true) ||
contains(to_string($message.message), "logged out", true)
then
set_field("mt_service", "account");
end
rule "MT service dhcp"
when
contains(to_string($message.message), "dhcp", true)
then
set_field("mt_service", "dhcp");
end
rule "MT service firewall"
when
contains(to_string($message.message), "firewall", true)
then
set_field("mt_service", "firewall");
end
Третий параметр true в contains() включает регистронезависимый поиск.
Для добавления нового сервиса: найти в Search записи с mt_service: other, определить ключевое слово, создать новое правило в Stage 1.
Почему keyword matching, а не парсинг topic
MikroTik в BSD syslog режиме не передаёт topics (system, firewall, l2tp и т.д.) как структурированные поля. Topic встроен в текст сообщения в непредсказуемом формате. Переключение на RFC 5424 (non-BSD) формат может не работать корректно с Graylog. Keyword matching — надёжный и расширяемый подход для данного случая.
Итоговая иллюстрация пути лога
Источник 2: Серверные логи (Vector → GELF)
Установка Vector на сервере
bash -c "$(curl -L https://setup.vector.dev)"
sudo apt install vector -y
Или через deb-пакет:
curl -LO https://github.com/vectordotdev/vector/releases/latest/download/vector_<version>_amd64.deb
sudo dpkg -i vector_<version>_amd64.deb
Конфигурация Vector
Файл: /etc/vector/vector.yaml
sources:
journald:
type: journald
mysql_error:
type: file
include:
- /var/log/mysql/error.log
mysql_slow:
type: file
include:
- /var/lib/mysql/*-slow.log
multiline:
start_pattern: '^# Time:|^# User@Host:'
mode: halt_before
condition_pattern: '^# Time:|^# User@Host:'
timeout_ms: 1000
transforms:
tag_system:
type: remap
inputs:
- journald
source: |
.log_type = "system"
.source_host = get_hostname!()
tag_mysql:
type: remap
inputs:
- mysql_error
- mysql_slow
source: |
.log_type = "mysql"
.source_host = get_hostname!()
sinks:
graylog:
type: socket
inputs:
- tag_system
- tag_mysql
address: "172.20.109.253:12201"
mode: udp
encoding:
codec: gelf
Ключевой элемент — секция transforms, где Vector проставляет поле log_type ("system" или "mysql") до отправки в Graylog. Это поле используется pipeline'ом для маршрутизации.
Управление Vector
sudo vector validate /etc/vector/vector.yaml # проверка конфига
sudo systemctl enable --now vector # запуск
sudo journalctl -u vector -f # просмотр логов
Чекпоинты и устойчивость
Vector хранит позицию чтения (checkpoint) в /var/lib/vector/. При перезапуске продолжает с того места где остановился. При падении сервера и последующей загрузке Vector отправит все пропущенные сообщения из journald включая записи о причине падения (kernel panic, OOM killer).
Input в Graylog
Тип: GELF UDP, порт: 12201. Обязательно проверить проброс порта 12201:12201/udp в docker-compose.
Как выглядит сырое сообщение от Vector
Пример системного лога (journald → Vector → GELF → Graylog):
{
"timestamp": "2026-04-13T12:17:01.096Z",
"source": "ideaconc-idea-website-database",
"message": "(root) CMD (cd / && run-parts --report /etc/cron.hourly)",
"log_type": "system",
"source_host": "ideaconc-idea-website-database",
"source_type": "journald",
"SYSLOG_IDENTIFIER": "CRON",
"SYSTEMD_UNIT": "cron.service",
"SYSTEMD_SLICE": "system.slice",
"SYSTEMD_CGROUP": "/system.slice/cron.service",
"CMDLINE": "/bin/sh -c \"cd / && run-parts --report /etc/cron.hourly\"",
"EXE": "/usr/bin/dash",
"COMM": "sh",
"PID": "154760",
"UID": "0",
"GID": "0",
"PRIORITY": "6",
"SYSLOG_FACILITY": "9",
"SYSLOG_PID": "154760",
"SYSLOG_TIMESTAMP": "Apr 13 12:17:01",
"BOOT_ID": "95473fbb6f4144f28a646fa5019a354f",
"MACHINE_ID": "77fc4b77772f42b198159ebc5b6fb325",
"RUNTIME_SCOPE": "system",
"SELINUX_CONTEXT": "unconfined",
"AUDIT_SESSION": "4142",
"AUDIT_LOGINUID": "0",
"CAP_EFFECTIVE": "1ffffffffff",
"SYSTEMD_INVOCATION_ID": "96050e21f2f1471a8a6c76b91758a78d",
"gl2_source_input": "69d8fec885ff8306a5fc1252",
"gl2_remote_ip": "172.18.10.6",
"streams": ["69d90ac785ff8306a5fc3322", "69d8ff6e85ff8306a5fc15bc"]
}
Поля log_type, source_host, source_type — проставлены Vector'ом. Поля с заглавными буквами (SYSTEMD_UNIT, SYSLOG_IDENTIFIER и т.д.) — метаданные journald. Поля gl2_* — добавлены Graylog при приёме.
Pipeline: Vector Router (подключён к Default Stream)
rule "Route system logs"
when
has_field("log_type") AND to_string($message.log_type) == "system"
then
route_to_stream(name: "Servers", remove_from_default: true);
end
rule "Route MySQL logs"
when
has_field("log_type") AND to_string($message.log_type) == "mysql"
then
route_to_stream(name: "MySQL", remove_from_default: true);
end
Флаг remove_from_default: true убирает сообщение из Default Stream, чтобы оно не дублировалось в Default Index Set.
Pipeline Rules — синтаксис и примеры
Структура правила
rule "Название правила"
when
<условие — boolean выражение>
then
<действия>
end
Ограничения синтаксиса
- Нет
else ifиelse— только простойif/then/endвнутри then-блока - Каждый
ifдолжен завершатьсяend;(точка с запятой после end) - Для реализации логики "если A — сделай X, если B — сделай Y" используйте отдельные правила в одном Stage
Полезные функции
| Функция | Описание |
|---|---|
has_field("name") | Проверяет наличие поля |
to_string($message.field) | Получает значение поля как строку |
contains(str, substr, ignore_case) | Проверяет содержание подстроки (третий параметр true = регистронезависимо) |
set_field("name", value) | Создаёт или перезаписывает поле |
route_to_stream(name: "...", remove_from_default: true) | Направляет в стрим |
grok(pattern, string) | Парсит строку по grok-паттерну |
split(delimiter, string) | Разбивает строку на массив |
Стратегия: Stages vs Rules
Stage 0 — извлечение полей, установка дефолтных значений. Выполняется первым.
Stage 1+ — классификация, маршрутизация. Использует поля, установленные на Stage 0.
Правила внутри одного Stage выполняются параллельно. Если несколько правил совпали — все выполнятся (последнее перезапишет поле).
Куда подключать Pipelines
- Pipeline маршрутизации (route_to_stream) → подключать к Default Stream
- Pipeline обогащения (set_field, grok) → подключать к целевому стриму
Index Sets — управление хранением
Рекомендуемая конфигурация
| Index Set | Префикс | Retention | Назначение |
|---|---|---|---|
| Default | graylog | 40 дней | Нераспределённые сообщения |
| Network | mikrotik | 7 дней | Сетевое оборудование (MikroTik, SNR, Kerio) |
| Servers | servers | 30 дней | Системные логи (journald) |
| MySQL | mysql | 60 дней | MySQL error/slow logs |
| Applications | apps | 30-60 дней | Laravel, Next.js, другие приложения |
Почему разные Index Sets
- Разный retention — сетевые логи теряют актуальность за дни, slow queries полезны месяцами
- Оптимизация поиска — при запросе к стриму OpenSearch просматривает только релевантные индексы
- Управление диском — видно какой тип логов занимает больше всего места
Параметры для single-node
- Shards: 1 (больше не нужно на одном ноде)
- Replicas: 0 (нет второго нода для реплик)
- Rotation strategy: Data Tiering (дефолт Graylog 7)
Рекомендации по масштабированию
Добавление нового MikroTik
- Задать identity:
/system/identity/set name=shop-NNN-gw - Настроить logging action на Graylog IP/порт
- Логи автоматически попадут в стрим Mikrotik через существующий pipeline
Добавление нового сервера
- Установить Vector нативно (
apt install vector) - Скопировать шаблон
/etc/vector/vector.yamlс нужными sources - Указать
log_typeв transforms для каждого источника - Логи автоматически попадут в нужные стримы через pipeline "Vector Router"
Добавление Laravel (idea-store)
Вариант 1 — GELF напрямую из Laravel через Monolog:
// config/logging.php
'graylog' => [
'driver' => 'monolog',
'handler' => \Monolog\Handler\GelfHandler::class,
'handler_with' => [
'publisher' => new \Gelf\Publisher(
new \Gelf\Transport\UdpTransport('graylog-server', 12201)
),
],
'level' => 'debug',
],
Вариант 2 — Vector читает storage/logs/laravel.log и шлёт в Graylog. Проще в настройке, но теряются structured-поля.