Syslog-ng в Astra Linux Special Edition

Всем привет! Меня зовут Михаил, и в своей предыдущей статье я кратко осветил цепочку прохождения логов в нашей ОС Astra Linux SE. Продолжаем!

Любой человек, который регулярно сталкивается с темой логирования, рано или поздно задаётся вопросом: «А что ещё можно сделать с логами, помимо простого добавления записей в некоторый файл?». Поэтому сейчас поговорим о таком мощном инструменте обработки логов, как syslog-ng.

Краткий экскурс в историю

До 1998 года распространённой службой (демоном) логирования в Linux-системах была утилита syslogd (в некоторых источниках можно также встретить название sysklogd). В качестве одного из ключевых недостатков данной утилиты часто выделяют отсутствие гибкости в настройке, вызванное, в частности, трудночитаемым форматом конфигурации.

В 1998 году появилась первая версия syslog-ng. Подразумевалось, что данная служба заменит устаревший на тот момент syslogd. Название службы интерпретируется как «Syslog нового поколения». Одно из значимых нововведений в syslog‑ng — собственный формат конфигурации, несовместимый с конфигурацией syslogd.

Но были у syslog-ng и недостатки. В частности, служба изначально имела открытое ядро и проприетарную часть, и значительная часть функциональности на тот момент была как раз проприетарной. Также не всем пришлась по душе несовместимость формата конфигурации syslog-ng и syslogd.

Так в 2004 году в качестве прямого конкурента syslog-ng появился rsyslog. Служба изначально была совершенно свободной, поддерживала полную совместимость с конфигурацией syslogd и на долгие годы де-факто стала стандартом в большом количестве linux-систем. Syslog-ng же предлагался в качестве альтернативы.

Поворотной точкой в развитии syslog-ng стал выход в 2008 году версии 3.0, начиная с которой проприетарная и свободная редакции развиваются параллельно. В итоге на текущий момент syslog-ng и rsyslog по базовой функциональности примерно идентичны.

Добавлю, что ещё одной точкой развития логирования в Linux стало появление systemd-journald. Служба реализует концепцию, которую можно описать фразой «все логи в одном месте». Изначально подразумевалось, что она заменит весь «зоопарк» других аналогичных служб. В частности, в Debian 12 по умолчанию устанавливается только systemd-journald. Однако у данной службы (демона) есть свои недостатки. Поэтому отказ от классических демонов логирования на текущий момент не предвидится и в большинстве современных Linux-систем они дополняют systemd-journald, работая в связке с ним.

Как мы пришли к syslog-ng в Astra Linux

Давние пользователи ОС Astra Linux, вероятно, знают, что изначально за логирование в нашей ОС отвечал rsyslog. Так как же получилось, что в Astra Linux SE 1.7.1 мы, так сказать, «на переправе» заменили его на syslog-ng?
Ответ: требования регуляторов к защите информации.

Для закрытия некоторых требований регуляторов нам нужно было реализовать простую обработку логов из разных источников. На тот момент требований было немного, но уже тогда команда поняла, что со временем их станет больше. А также станет больше логов, которые нужно будет обрабатывать тем или иным образом. К слову, наш прогноз в итоге сбылся в полной мере.

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

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

Далее последовали длительные поиски подходящего решения. Рассматривалось также написание собственного демона логирования, но в результате мы остановились на syslog-ng.

Почему мы выбрали его?

  1. Syslog-ng из коробки может работать со многими стандартными и нестандартными источниками логов.

  2. Он способен на плагинное расширение на языках C и Python (в некоторых источниках заявляется возможность писать плагины на Java). При этом написание плагинов на Python максимально простое.

  3. У syslog-ng простой и невероятно гибкий формат конфигурации.

  4. Он прекрасно задокументирован и имеет прозрачную историю изменений.

  5. Ядро syslog-ng распространяется по лицензии LGPL. Сам проект исторически имеет как свободную, так и проприетарную части.

  6. Syslog-ng активно используется в некоторых предприятиях. Например, я впервые познакомился с данной службой, когда работал в сфере телекоммуникаций.

Проект на текущий момент динамично развивается. Исправляются различные ошибки и уязвимости. Добавляются новые функции. В частности, в syslog-ng 4.x, который на момент написания статьи тестируется сообществом Debian, существенно улучшили возможности по работе с плагинами.

Что мы можем делать с syslog-ng?

1. Мы можем его настраивать!

Основным принципом настройки syslog-ng является описание объектов конфигурации, каждый из которых отвечает за ту или иную обработку лога и выстраивание из них цепочек обработки. По ним, словно по конвейеру, проходят записи.

Каждую исходную запись лога syslog-ng с некоторой степенью точности разбивает на пары ключ-значение, которые серьезно упрощают дальнейшую обработку. Доступ к значениям ключей можно осуществлять с помощью макросов. Стандартных макросов syslog-ng достаточно много, и в примерах ниже мы рассмотрим лишь некоторые из них.

На текущий момент в syslog-ng имеются 4 основных объекта конфигурации:

  • source – источник, из которого будут приниматься записи лога: сокет (стандартный или не стандартный), файл, сеть, stdout приложения и т.д;

  • filter – позволяет исключать из обработки лишние записи;

  • parser – производит дополнительное разбиение записи на пары ключ-значение; так же, как и фильтр, позволяет исключить из дальнейшей обработки лишние записи;

  • destination – точка назначения, в которую попадёт конечная запись после всех обработок: сокет (стандартный или не стандартный), файл, сеть, stdin приложения и т.д.;

  • log – цепочка обработки, в которой выстраиваются объекты от source до destination.

Основная конфигурация syslog-ng находится в файле /etc/syslog-ng/syslog-ng.conf. К редактированию данного файла в Astra Linux Special Edition рекомендуется подходить с осторожностью. В частности, не рекомендуется удалять заранее настроенные объекты конфигурации. При этом можно свободно редактировать и удалять цепочки обработки логов в файле.

Дополнительные настройки рекомендуется размещать в файлах в каталоге /etc/syslog-ng/conf.d.

Рассмотрим несколько примеров.

Классический пример

Начнём с базового примера передачи и приёма логов по протоколу UPD (syslog-ng также поддерживает протокол TCP). Будем посылать логи Syslog с клиентского хоста на сервер и там складывать их в каталог "/var/log/remote/<ip хоста>/syslog".

Поскольку источник логов Syslog настроен в syslog-ng по умолчанию и имеет имя s_src, то на клиентском хосте нам для начала будет достаточно настроить удаленный сервер в качестве точки назначения и цепочку обработки, принимающую логи из источника s_src, и передающую в нашу точку назначения.

Создадим в каталоге /etc/syslog-ng/conf.d файл с именем, например, test-client.conf со следующим содержимым:

destination d_udp { udp("<ip-сервера>" port(<порт, который слушает сервер>) flags(syslog-protocol)); };
log { source(s_src); destination(d_udp); };

Чтобы конфигурация вступила в силу, достаточно перезапустить syslog-ng.

В приведённой выше конфигурации есть нюанс. Дело в том, что по умолчанию syslog-ng для приёма и передачи логов по сети использует протокол RFC3164. Флаг syslog-protocol указывает, что для отправки и/или приёма следует использовать протокол IETF-syslog, соответствующий RFC5424-26.

Теперь рассмотрим конфигурацию сервера. На сервере достаточно будет настроить источник, принимающий логи по сети, точку назначения в виде файла /var/log/remote/<ip хоста>/syslog и соответствующую цепочку обработки логов.

Создадим на сервере, например, файл /etc/syslog-ng/conf.d/test-server.conf со следующим содержимым:

source s_udp { udp(port(<любой свободный порт>) flags(syslog-protocol)); };

destination d_remote {
    file(
        "/var/log/remote/${HOST}/syslog"
        create-dirs(yes)
    );
};

log { source(s_udp); destination(d_remote); };

Здесь стандартный макрос syslog-ng ${HOST} позволяет получить из записи ip-адрес хоста, с которого пришёл лог. Опция create-dirs(yes) указывает, что нужно создавать каталоги, если они не существуют.

Пытливый читатель спросит: «А что, если я хочу получать имя хоста вместо его ip-адреса?».

Всё просто! Достаточно в настройках сервера указать опцию источника keep-hostname(yes). Выглядеть это будет примерно так:

source s_udp {
    udp(
        port(<любой свободный порт>)
        keep-hostname(yes)
        flags(syslog-protocol)
    );
};

Итак, выше мы рассмотрели пример обработки стандартных логов Syslog. Однако довольно часто возникает вопрос обработки с помощью syslog-ng нестандартных логов. И для этого у нас имеются достаточно широкие возможности, ведь недаром официальное руководство администратора занимает порядка тысячи страниц.

Чаще всего приложения, которые не используют стандартные средства Syslog, пишут свои логи в некий файл. Реже можно выводить логи в stdout. Рассмотрим оба этих случая.

Второй пример, в котором обнаруживается подвох

Предварительно для удобства надо создать файл "/etc/syslog-ng/conf.d/test-local.conf".

Чтобы syslog-ng начал читать и обрабатывать лог из файла, достаточно настроить источник и включить его в цепочку обработки логов. Например:

source s_dpkg {
    file("/var/log/dpkg.log");
};

В описанном случае мы будем читать лог утилиты dpkg. Можно перенаправить его весь так же, как и логи Syslog из вышеприведенного примера. Но давайте попробуем что-нибудь поинтереснее.

К примеру, мы хотим перенаправлять куда-то только данные об успешно установленных пакетах. Пусть это будет файл. Для этого нам нужно отфильтровать всё лишнее.

Начнём всё же с конфигурации, которая будет просто перекладывать лог dpkg из одного файла в другой. Источник мы уже описали выше. Добавим к нему точку назначения и цепочку обработки логов.

destination d_test_dpkg { file("/var/log/test-dpkg.log"); };
log { source(s_dpkg); destination(d_test_dpkg); };

Как только мы попробуем перезапустить syslog-ng, чтобы описанная конфигурация вступила в силу, то с удивлением обнаружим: демон не стартует. Как же так?

Всё дело в том, что я умышленно выбрал файл "/var/log/dpkg.log" в качестве примера. В Astra Linux Special Edition, начиная с версии 1.7.2, уже имеется настроенный источник с именем astra_dpkg_src для чтения указанного файла.

И здесь вскрывается один из важных принципов syslog‑ng: одновременно в конфигурации не могут существовать два одинаковых объекта, если они не имеют уникального идентификатора.

У читателя возникнет вопрос: но ведь у нас разные идентификаторы источников s_dpkg и astra_dpkg_src?

Подвох тут в том, что так называемый драйвер – а в нашем случае это драйвер чтения файла file(), – тоже является объектом конфигурации. Драйверы, читающие один и тот же файл, syslog-ng считает одинаковыми объектами. Для того, чтобы научить его отличать один драйвер от другого, нам достаточно указать для источника s_dpkg опцию persist-name():

source s_dpkg {
    file(
        "/var/log/dpkg.log"
        persist-name("s_dpkg")
    );
};

С такими настройками источника syslog-ng перезапустится и примет новую конфигурацию. А как только мы, например, установим какой-нибудь пакет, то у нас появится файл "/var/log/test-dpkg.log".

Но просмотр содержимого файла снова нас удивит. Записи в логе будут иметь примерно следующий вид:

Aug 15 10:25:29 172-max 2024-08-15 10:25:28 status half-configured hicolor-icon-theme:all 0.17-2
Aug 15 10:25:29 172-max 2024-08-15 10:25:28 status installed hicolor-icon-theme:all 0.17-2

Откуда взялась лишняя информация, такая как дублирующаяся дата и имя хоста?

Дело в том, что по умолчанию syslog-ng любые записи логов пытается парсить и приводить в соответствие со стандартом Syslog.

Чтобы отключить предварительный парсинг и оставить записи лога dpkg без изменений, в настройках источника нам нужно выставить флаг no-parse:

source s_dpkg {
    file(
        "/var/log/dpkg.log"
        persist-name("s_dpkg")
        flags(no-parse)
    );
};

Чтобы записи в конечном файле не приводились в соответствие стандарту Syslog, можно задать шаблон форматирования записи в настройках точки назначения:

destination d_test_dpkg {
    file(
        "/var/log/test-dpkg.log"
        template("${MESSAGE}\n")
    );
};

Здесь мы с помощью стандартного макроса ${MESSAGE} указываем, что нужно брать только «тело» исходной записи, предварительный парсинг которого мы отключили ранее.

Наконец-то можно приступить к фильтрации записей!

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

2024-08-15 10:50:26 status installed xserver-xorg-core:amd64 2:1.20.14-1ubuntu1astra.se43

Соответственно, мы можем в сообщении искать подстроки вида "status installed" и пропускать только те записи, которые их содержат.

Создадим фильтр, который будет проверять значение MESSAGE на соответствие регулярному выражению:

filter installed_filter {
    match("status installed" value("MESSAGE"));
};

И вставим его в цепочку обработки логов между источником и точкой назначения:

log { source(s_dpkg); filter(installed_filter); destination(d_test_dpkg); };

Вуаля! Теперь мы имеем конфигурацию, которая перенаправляет записи об установленных пакетах из исходного лога dpkg в новый.

Третий пример, в котором есть непрошенная цикличность

Как я писал ранее, некоторые программы могут писать свои логи в stdout. Соответственно, syslog-ng умеет их оттуда забирать.

Одним из способов прочитать stdout программы является использование источника program(). Данный источник автоматически запускает указанный в его настройках исполняемый файл и читает его stdout.

Для примера попробуем прочитать stdout простого bash-скрипта:

#!/bin/bash
echo "Hello World!"

Для этого создадим в домашнем каталоге файл test.sh c указанным выше содержимым и наделим его правами на исполнение.

Далее опишем в файле /etc/syslog-ng/conf.d/test-local.conf следующую конфигурацию:

source s_program {
    program("/home/<имя пользователя>/test.sh");
};

destination d_program {
    file("/var/log/test-program.log");
};

log { source(s_program); destination(d_program); };

При перезапуске syslog-ng мы получим интересный эффект. Источник будет циклично перезапускать наш скрипт и непрерывно сыпать в файл записи вида:

Aug 15 11:45:07 172-max Hello World!

Вы уже, наверно, догадались, что я люблю примеры с подвохом. И здесь подвох в том, что источник program рассчитан на программы, которые в идеале должны работать непрерывно. Например, это могут быть системные службы.

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

На этом можно было бы завершить наше предельно сжатое рассмотрение примеров конфигурации syslog-ng, но мы ещё не попробовали такой мощный инструмент, как парсер. Здесь как раз пригодится вышеописанный пример с bash-скриптом.

Четвертый пример, где парсер выходит в свет

Допустим, мы хотим направлять в конечный лог не всю запись, а только какую-то её часть. И это одно из основных (но не единственных) применений для парсеров.

Рассмотрим это на примере стандартного парсера syslog-ng «ключ-значение» или kv-parser(). Данный парсер служит для разбиения записей вида "KEY1=value1,KEY2=value2,..." или "KEY1=value1 KEY2=value2 ...".

Доработаем наш bash-скрипт:

#!/bin/bash
echo "action=Hello, who=World!"

После этого в файл /var/log/test-program.log будут попадать записи вида:

Aug 15 12:51:04 172-max action=Hello, who=World!

Допустим, мы хотим, чтобы в конечный лог попадали записи вида "Hello World!".

Доработаем нашу конфигурацию следующим образом:

source s_program {
    program("/home/splr11/test.sh" flags(no-parse));
};

parser parser_test_kv { kv-parser(); };

destination d_program {
    file(
        "/var/log/test-program.log"
        template("${action} ${who}\n")
    );
};

log { source(s_program); parser(parser_test_kv); destination(d_program); };

Здесь, как и в примере с перенаправлением лога dpkg, мы отключили предварительный парсинг исходной записи и ввели в цепочку обработки логов наш собственный парсер. Также описали шаблон форматирования, в который добавили полученные на выходе парсера нестандартные макросы ${action} и ${who}.

После применения конфигурации мы получим в конечном логе желаемый результат.

Пятый пример, где мы отдыхаем от файлов логов

В предыдущих примерах мы передавали логи по сети, но чаще всего просто записывали в файлы. Всё дело в том, что запись в файл является наиболее простой и в то же время достаточно наглядной функцией syslog-ng. Но подуставший от файлов читатель законно спросит: "А где же заявленное разнообразие функций?" И на это у меня есть ответ: а давайте попробуем для разнообразия записать наши логи в базу данных!

Для этого будем использовать СУБД PostgreSQL или одну из её производных. К слову, одной из СУБД на базе PostgreSQL является СУБД Tantor, разрабатываемая одноименной компанией, входящей в "Группу Астра". Также хочу особо подчеркнуть, что именно PostgreSQL мои коллеги-разработчики Astra Linux адаптировали для корректного взаимодействия со встроенными в операционную систему средствами защиты информации.

Здесь я не буду вдаваться в детали установки и настройки PostgreSQL или Tantor. Будем считать, что на локальном хосте уже установлен минимально настроенный сервер СУБД, а также установлен модуль syslog-ng, позволяющий взаимодействовать с базами данных. Для этого достаточно, чтобы были установлены пакеты postgresql и syslog-ng-mod-sql из репозиториев Astra Linux и сторонний пакет libdbd-pgsql, который для ALSE 1.8.1 можно взять, например, из репозиториев Debian 12, а для ALSE 1.7 — из репозиториев Debian 10; или можно собрать пакет из исходного кода. Также будем считать, что нам достаточно стандартного пользователя по умолчанию — postgres, для которого задан какой-нибудь пароль.

Итак, создадим тестовую базу данных:

sudo -u postgres createdb test_log

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

После небольшой доработки получим следующую конфигурацию:

source s_program {
    program("/home/splr11/test.sh" flags(no-parse));
};

parser parser_test_kv { kv-parser(); };

destination d_sql {
    sql(
        type(pgsql)
        host("127.0.0.1") username("postgres") password("$(env DB_PASSWD)")
        database("test_log")
        table("messages_${HOST}")
        columns("datetime", "host", "action", "who")
        values("${R_DATE}", "${HOST}", "${action}", "${who}")
        indexes("datetime", "host")
    );
};

log { source(s_program); parser(parser_test_kv); destination(d_sql); };

Здесь у нас в базе данных test_log автоматически будет создана новая таблица, содержащая имя хоста в своём названии, для которой в дальнейшем будут отправляться запросы INSERT. В состав таблицы мы включили дату, когда syslog-ng принял сообщение, о чём свидетельствует макрос ${R_DATE}, также включили имя хоста и уже знакомый нам результат парсинга. Кроме того, для полей даты и имени хоста мы добавили индексы.

По умолчанию все поля имеют тип text. Но его также можно указывать самостоятельно при определении опции columns(). Например:

columns("datetime varchar(255)", "host varchar(255)", "action varchar(16)", "who varchar(16)")

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

psql -U postgres -h localhost test_log

И здесь мы плавно подошли к одному из неозвученных ранее нюансов работы syslog-ng.

Я уже обозначил, что имя таблицы с логами включает в себя имя хоста. Для своего тестового стенда я сделал его следующим: "alse-18x-max". Если же мы, находясь в консоли СУБД, выполним команду вывода списка таблиц, то увидим интересную картину:

test_log=# \d
                  Список отношений
 Схема  |          Имя          |   Тип   | Владелец 
--------+-----------------------+---------+----------
 public | messages_alse_18x_max | таблица | postgres

Дело в том, что syslog-ng в некоторых случаях автоматически меняет символ дефиса на символ нижнего подчеркивания, и это нужно иметь в виду. Один из таких случаев как раз представлен выше.

Соответственно, для просмотра содержимого таблицы мы можем выполнить простой запрос SELECT, как, например, в моём случае:

SELECT * FROM messages_alse_18x_max;

Получим примерно следующий результат:

    datetime     |     host     | action |  who   
-----------------+--------------+--------+--------
 Sep  2 23:10:14 | alse-18x-max | Hello  | World!
 Sep  2 23:10:14 | alse-18x-max | Hello  | World!
 Sep  2 23:10:14 | alse-18x-max | Hello  | World!

Внимание! По окончанию эксперимента лучше удалить или закомментировать все цепочки обработки логов, приводящие к лавинообразному заполнению точек назначения. Также лучше подчистить все накопившиеся тестовые логи.
Теперь точно можно завершить ознакомление с настройкой syslog-ng и перейти к другим примерам.

2. Мы можем его расширять!

Стандартный инструментарий обработки логов, предоставляемый syslog-ng, очень широк. Но бывают случаи, когда и его не хватает. Например, когда нужно решать какие-то специфические задачи. Тогда нам на помощь приходит возможность расширения с помощью плагинов.

Syslog-ng позволяет писать плагины на языках C, Python и Java. При этом можно записывать код на Python прямо в конфигурации syslog-ng! Но всё же рекомендую так делать исключительно в экспериментальных и отладочных целях.

Для работы с плагинами на Python в системе должен быть установлен пакет syslog-ng-mod-python.

Сразу обозначу один чрезвычайно важный момент. Недавно выпустили новое поколение ОС Astra Linux Special Edition 1.8. При этом широкое распространение пока ещё имеет версия 1.7. И если всё, что описано выше, актуально для обеих версий ОС, то при написании плагинов становятся явными различия syslog-ng в этих двух версиях.

В ALSE 1.7 syslog-ng имеет версию 3.19. Для ALSE 1.8 актуален syslog-ng 3.38. При этом версия 3.19 по умолчанию использует для плагинов Python 2, в то время, как версия 3.38 штатно работает уже с Python 3. Это в первую очередь влечет за собой нюансы, связанные с типизацией данных, а также с используемыми функциями языка. Поэтому ниже я по возможности буду приводить примеры как для syslog-ng 3.19, так и для syslog-ng 3.38.

Попробуем записать что-нибудь в Syslog

Раз уж мы плавно подошли к теме программирования, осветим мимоходом, что нужно сделать для формирования записи, например, в стандартном логе /var/log/syslog. К тому же syslog-ng при использовании bash-скрипта, который был рассмотрен ранее, порождает нагрузку на систему.

Для дальнейших примеров буду также использовать формат записи "KEY1=value1, KEY2=value2, ...".

Чтобы отправить запись в Syslog достаточно простейшей программы на С:

#include <syslog.h>

int main(int argc, char **argv) {
    syslog(LOG_NOTICE, "action=Hello, who=World!");
    return 0;
}

Или то же самое мы можем сделать на Python:

import syslog

if __name__ == '__main__':
    syslog.syslog('action=Hello, who=World!')

После каждого запуска любой из этих программ в файле /var/log/syslog будут появляться записи следующего вида:

Aug 18 12:26:25 181-max testsyslog.py[4720]: action=Hello, who=World!

Теперь обработаем наши записи

На языке Python мы можем писать собственные источники, парсеры и точки назначения, о чём подробно рассказано в руководстве администратора syslog-ng.

Для примера напишем на Python собственную точку назначения, которая будет писать в некий файл записи вида «Hello World!». При этом пусть имя целевого файла можно будет задавать в опциях точки назначения. В целях эксперимента сделаем это сразу в конфигурации syslog‑ng, для чего создадим файл «/etc/syslog‑ng/conf.d/test‑python.conf».

Здесь потребуется брать записи только от нашей программы на С или скрипта на Python. Будем считать, что программа называется «testsyslog», а скрипт, соответственно, «testsyslog.py».

В этом случае нам ничего не помешало бы описать фильтр syslog-ng и добавить его в цепочку обработки логов:

filter test_filter { program("testsyslog") or program("testsyslog.py"); };

Но в целях демонстрации возможностей плагинов возложим фильтрацию на нашу точку назначения.

При этом парсинг записи лучше оставить отдельному парсеру. Пока воспользуемся описанным ранее парсером kv-parser(). Если на момент написания точки назначения останется действующая конфигурация из предыдущих примеров, то можно будет воспользоваться парсером из неё. В противном случае парсер придется описать заново. При описании конфигурации ниже я для удобства буду считать, что ранее никаких других конфигураций, кроме тех, что идут при установке системы, описано не было.

И начнём мы с примера для самой новой версии нашей ОС - 1.8.1 и, соответственно, для syslog-ng 3.38.

Руководство администратора для syslog-ng 3.38 устанавливает следующее описание точек назначения на Python:

destination <имя точки назначения> {
    python(
        class("<имя класса Python, который реализует точку назначения>")
    );
};

python {
class <имя класса Python, который реализует точку назначения>(object):
    def open(self):
        """Установить соединение с целевым ресурсом
        Требуется вернуть False в случае провала"""
        return True

    def close(self):
        """Закрыть соединение с целевым ресурсом"""
        pass

    def is_opened(self):
        """Проверить соединение на доступность передачи сообщений"""
        return True

    def init(self, options):
        """Метод вызывается в процессе инициализации
        Требуется вернуть False в случае провала"""
        return True

    def deinit(self):
        """Метод вызывается в процессе деинициализации"""
        pass

    def send(self, msg):
        """Отправить сообщение в целевой ресурс
        Требуется вернуть True в случае успеха.
        Возврат False 'замомрозит' точку назначения на время,
        определенное опцией time-reopen()."""
        return True

    def flush(self):
        """Записать в целевой ресурс сообщения из очереди сообщений"""
        pass
};

При описании класса Python, обязательный только метод send(). Все остальные методы являются необязательными. Это актуально и для syslog-ng 3.19.

Опишем в ранее созданном файле следующую конфигурацию:

python {
class TestDestination(object):
    def __init__(self):
        self.__filename = None
        self.__file = None

    def init(self, options):
        self.__filename = options.get('filename', '/var/log/test-python.log')
        return True

    def open(self):
        try:
            self.__file = open(self.__filename, 'a+')
        except Exception as e:
            print(e)
            return False
        return True

    def send(self, msg):
        try:
            if msg['PROGRAM'] == b'testsyslog' or msg['PROGRAM'] == b'testsyslog.py':
                self.__file.write(f"{msg['action'].decode('utf-8')} {msg['who'].decode('utf-8')}\n")
                self.__file.flush()
        except Exception as e:
            print(e)
            return False
        return True

    def close(self):
        if self.__file:
            self.__file.close()
};

parser parser_test_kv { kv-parser(); };

destination d_test_python {
    python(
        class("TestDestination")
        options("filename" "/var/log/test-python-opt.log")
    );
};

log { source(s_src); parser(parser_test_kv); destination(d_test_python); };

После применения указанной конфигурации, при каждом вызове нашего скрипта или программы в файл «/var/log/test‑python‑opt.log» будут попадать записи вида «Hello World!».

Теперь пройдемся по подводным камням данного кода.

Во‑первых, здесь я добавил обработку опции «filename» (имя на самом деле может быть произвольным). И догадливый читатель заметит, что аргумент options в методе init() является словарем.

Во‑вторых, аргумент msg, хотя его обработка в общем случае и похожа на обработку словаря, на самом деле словарем не является. Данный аргумент — объект класса LogMessage, который является внутренним классом syslog‑ng. Поэтому нужно быть внимательными при попытке работы с объектом msg как с полноценным словарем, иначе можно наткнуться на разнообразные ошибки.

В‑третьих, в syslog‑ng 3.38 значения ключей объекта msg являются последовательностью байт и в некоторых случаях могут потребовать перекодирования в строку utf-8.

Теперь проделаем то же самое для syslog-ng 3.19.

В случае приведенного кода отличий будет минимум:

  1. В syslog-ng 3.19 отсутствует метод flush(). Кроме того, хотя метод open() и описан, но во время моих экспериментов он по какой-то причине не вызывался, и потому в примере ниже я отказался от его использования.

  2. Значения ключей объекта msg являются строками и не требуют дополнительных преобразований.

  3. Как я писал ранее, syslog-ng 3.19 по умолчанию работает с Python 2.

Поэтому конфигурация примет вид:

python {
class TestDestination(object):
    def __init__(self):
        self.__filename = None
        self.__file = None

    def init(self, options):
        self.__filename = options.get('filename', '/var/log/test-python.log')
        try:
            self.__file = open(self.__filename, 'a+')
        except Exception as e:
            print(e)
            return False
        return True

    def send(self, msg):
        try:
            if msg['PROGRAM'] == 'testsyslog' or msg['PROGRAM'] == 'testsyslog.py':
                self.__file.write("{} {}\n".format(msg['action'], msg['who']))
                self.__file.flush()
        except Exception as e:
            print(e)
            return False
        return True

    def close(self):
        if self.__file:
            self.__file.close()
};

parser parser_test_kv { kv-parser(); };

destination d_test_python {
    python(
        class("TestDestination")
        options("filename" "/var/log/test-python-opt.log")
    );
};

log { source(s_src); parser(parser_test_kv); destination(d_test_python); };

Далее считаю своим долгом рассмотреть пример собственного парсера, чтобы проиллюстрировать, откуда же берутся все эти "action" и "who". Для простоты продублируем работу kv-parser().

И снова начнем с syslog-ng 3.38.

Вот как описывает парсеры на Python руководство администратора, что актуально и для syslog-ng 3.19:

parser <имя парсера> {
    python(
        class("<имя класса, реализующего парсер>")
    );
};

python {
class <имя класса, реализующего парсер>(object):
    def init(self, options):
        '''Опционально. Метод вызывается в процессе инициализации.'''
        return True

    def deinit(self):
        '''Опционально. Метод вызывается в процессе деинициализации.'''
        pass

    def parse(self, msg):
        '''Обязательно. Метод получает и обрабатывает сообщение.'''
        return True
};

Прежде чем писать свой парсер, отмечу, что одним из применений парсеров также является фильтрация записей. Она производится, когда метод parse() возвращает True или False. Поэтому логично будет перенести фильтрацию по имени программы из точки назначения в парсер.

Таким образом, наша конфигурация примет вид:

python {
# Добавили тестовый парсер
class TestParser(object):
    def parse(self, msg):
        try:
            if msg['PROGRAM'] != b'testsyslog' and msg['PROGRAM'] != b'testsyslog.py':
                return False
            pairs = msg['MESSAGE'].split(b', ')
            for pair in pairs:
                key, value = pair.split(b'=')
                msg[key] = value
        except Exception as e:
            print(e)
            return False
        return True

class TestDestination(object):
    def __init__(self):
        self.__filename = None
        self.__file = None

    def init(self, options):
        self.__filename = options.get('filename', '/var/log/test-python.log')
        return True

    def open(self):
        try:
            self.__file = open(self.__filename, "a+")
        except Exception as e:
            print(e)
            return False
        return True

    def send(self, msg):
        try:
            # Перенесли проверку имени программы в парсер
            self.__file.write(f"{msg['action'].decode('utf-8')} {msg['who'].decode('utf-8')}\n")
            self.__file.flush()
        except Exception as e:
            print(e)
            return False
        return True

    def close(self):
        if self.__file:
            self.__file.close()
};

# Заменили kv-parser() на python()
parser parser_test_kv { python(class("TestParser")); };

destination d_test_python {
    python(
        class("TestDestination")
        options("filename" "/var/log/test-python-opt.log")
    );
};

log { source(s_src); parser(parser_test_kv); destination(d_test_python); };

Как можно видеть, в процессе обработки записи лога мыможем «на ходу» добавлять к объекту msg новые пары ключ-значение, а также в некоторых случаях менять значения существующих ключей.

Для syslog-ng 3.19 доработки будут аналогичны с учетом вышеописанных особенностей этой версии, потому не будем заострять на них внимание.

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

3. Мы можем его отлаживать!

Выше мы рассмотрели значительный для неподготовленного специалиста объем конфигурации и программного кода. И рано или поздно перед любым встанет вопрос: а как мы можем определить, что что-то пошло не так?

В большинстве случаев (но не всегда) в случае ошибки syslog-ng просто не запустится. Но даже когда демон продолжит свою работу, всё равно существует потребность в выяснении причины сбоя. Для этого чаще всего используются два инструмента.

Для проверки корректности синтаксиса конфигурации нужно вызвать команду:

syslog-ng -s

В случае корректной конфигурации команда вернет в stdout пустую строку. В случае же наличия ошибок будет возвращено их описание.

Для вариантов и синтаксически корректной, и некорректной конфигурации, существует отладочный режим syslog-ng. Перед его запуском рекомендуется остановить работу syslog-ng в качестве демона.

Отладочный режим syslog-ng 3.38 в Astra Linux Special Edition 1.8 запускается командой:

syslog-ng -de --no-caps

Или для случая, когда не нужно выводить содержимое stderr:

syslog-ng -d --no-caps

Здесь опция "--no-caps" отключает некоторые проверки прав на файлы и используется нами в основном для диагностики подсистемы регистрации событий. В общем же случае ей можно пренебречь.

Для syslog-ng 3.19 отладочный режим запускается командой:

syslog-ng -d --no-caps

Ранее в коде наших плагинов на Python я оставил вывод исключений. Так вот посмотреть его можно как раз в отладочном режиме.

У отладочного режима есть недостаток. Дело в том, что его вывод зачастую бывает достаточно объемный. Разглядеть там нужную информацию бывает непросто. Тем не менее, если знать этот факт, отладочную информацию можно форматировать так, чтобы она гарантированно бросалась в глаза.

Если же необходимо видеть только содержимое stderr, но прочая отладочная информация при этом неважна, то в таком случае мы можем запустить syslog-ng в режиме обычного консольного приложения с указанием, что требуется дополнительно получать содержимое stderr:

syslog-ng -Fe --no-caps

Описанная выше команда актуальна как для syslog-ng 3.38, так и для syslog-ng 3.19.

Заключение

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

Кстати, особенно хочу отметить, что главным источником информации для написания статьи, кроме моментов, несвязанных напрямую с syslog-ng, стало упомянутое выше руководство. Это лишний раз подтверждает тезис, что syslog-ng прекрасно задокументирован.

Также я здесь снова упомянул подсистему регистрации событий и, фактически, поверхностно затронул причины, по которым мы в своё время взялись за её разработку.

Но это уже история для не менее объемной статьи. А потому продолжение следует...

Комментарии (1)


  1. WST
    27.09.2024 07:09

    Тот самый случай, когда понял, кто автор, ещё до того, как увидел его ник в коде