Пришла задача организовать сбор логов на почтовом сервере Postfix, который развёрнут на Ubuntu Server. Заказчика интересуют логи отправки писем в виде JSON. Он хочет, чтобы почтовый сервер на каждую отправку письма отчитывался — передавал по curl данные из лога. Поможет нам в этом непростом деле NXLog.
NXLog — это высокопроизводительное многоплатформенное решение для управления логами.
Решение существует как в бесплатном исполнении Community Edition, так и в платном — Enterprise Edition. Сравнение редакций:
https://nxlog.co/community-edition-vs-enterprise-edition
Бесплатная редакция полностью удовлетворяет всем требованиям нашей задачи:
- получение данных из файла и отправка в файл: модуль Files (im_file, om_file)
- преобразование данных в JSON: модуль JSON (xm_json)
- получение и отправка данных по HTTP: модуль HTTP (im_http, om_http)
Функции NXLog
- Непрерывный сбор событий операционной системы, брандмауэров, коммутаторов, маршрутизаторов, модемов, бизнес-систем и пр.
- Настраиваемая пересылка событий на другие Syslog сервера.
- Удаление, фильтрация выбранных или ненужных событий.
- Поиск, сортировка и настраиваемый фильтр событий.
Установка NXLog
У нас почтовый сервер Postfix работает на ОС Ubuntu 16.04.
Мануал по установке на Debian нам подойдёт:
https://docs.nxlog.co/userguide/deploy/debian.html
В списке можно найти нужную для нашей ОС версию дистрибутива. Однако, нам она не очень интересна, потому что не Community Edition.
Переходим на страничку загрузки пакетов для CE:
https://nxlog.co/downloads/nxlog-ce#nxlog-community-edition
Выбираем пакет для нашей операционной системы и скачиваем.
No thanks, just start my download.
Загружаем пакет на сервер и пробуем установить:
dpkg -i nxlog-ce_3.1.2319_ubuntu16_amd64.deb
И у нас ничего не устанавливается!
Есть проблемы зависимостей. Удаляем пакет:
apt-get remove nxlog-ce
Смотрим в документацию и видим волшебную надпись:
If dpkg returned errors about uninstalled dependencies, use apt-get to install them and complete the NXLog installation.
Ну ладно, ставим зависимости:
apt-get install libapr1
apt-get -f install
Повторно устанавливаем пакет NXLog:
dpkg -i nxlog-ce_3.1.2319_ubuntu16_amd64.deb
И у нас всё устанавливается!
Автоматически запускается служба nxlog:
service nxlog status
Конфигурационный файл находится по адресу:
/etc/nxlog/nxlog.conf
И там сейчас находится пример конфигурации.
Данный пример нам не нужен, стираем всё, будем настраивать обработку логов Postfix.
Отправка логов Postfix в файл
Для начала просто убедимся в том, что nxlog работает. Для этого организуем запись логов в файл: /var/log/smtp.log. А данные будем брать из логов Postfix: /var/log/mail.log.
Мы будем преобразовывать данные в формат JSON, в конфиге будет модуль xm_json:
<Extension _json>
Module xm_json
</Extension>
Для получения данных из файла /var/log/mail.log используем модуль im_file:
<Input postfix>
Module im_file
File "/var/log/mail.log"
<Exec>
if $raw_event =~ /(?x)^(\S+\ +\d+\ \d+:\d+:\d+)\ (\S+)
\ postfix\/(\S+)\[(\d+)\]:\ (.+)$/
{
$EventTime = parsedate($1);
$HostName = $2;
$SourceName = "postfix";
$Component = $3;
$ProcessID = $4;
$Message = $5;
if $Component == "smtpd" and
$Message =~ /(\w+): client=(\S+)\[([\d.]+)\]/
{
$QueueID = $1;
$ClientHostname = $2;
$ClientIP = $3;
}
if $Component == "cleanup" and
$Message =~ /(\w+): message-id=(<\S+@\S+>)/
{
$QueueID = $1;
$MessageID = $2;
}
if $Component == "qmgr" and
$Message =~/(\w+): from=(<\S+@\S+>), size=(\d+), nrcpt=(\w+)/
{
$QueueID = $1;
$Sender = $2;
$Size = $3;
$Nrcpt = $4;
}
if $Component == "smtp" and
$Message =~ /(?x)(\w+):\ to=(<\S+@\S+>),\ relay=([\w.]+)\[([\d.]+)\],
\ delay=(\d+),\ status=(\w+)\ \((\d+)\ \w+:\ queued\ as
\ (\w+)\)/
{
$QueueID = $1;
$Recipient = $2;
$RelayHostname = $3;
$RelayIP = $4;
$Delay = $5;
$Status = $6;
$SMTPCode = $7;
$QueueIDDelivered = $8;
}
}
</Exec>
</Input>
Целиком взял этот блок из документации.
Для записи данных в файл /var/log/smtp.log используем модуль om_file:
<Output out>
Module om_file
File "/var/log/smtp.log"
<Exec>
if $Component != "smtp" drop();
to_json();
</Exec>
</Output>
В итоге получаем:
########################################
# Global directives #
########################################
User nxlog
Group nxlog
include /etc/nxlog/nxlog.d/*.conf
LogFile /var/log/nxlog/nxlog.log
LogLevel INFO
########################################
# Modules #
########################################
<Extension _json>
Module xm_json
</Extension>
<Input postfix>
Module im_file
File "/var/log/mail.log"
<Exec>
if $raw_event =~ /(?x)^(\S+\ +\d+\ \d+:\d+:\d+)\ (\S+)
\ postfix\/(\S+)\[(\d+)\]:\ (.+)$/
{
$EventTime = parsedate($1);
$HostName = $2;
$SourceName = "postfix";
$Component = $3;
$ProcessID = $4;
$Message = $5;
if $Component == "smtpd" and
$Message =~ /(\w+): client=(\S+)\[([\d.]+)\]/
{
$QueueID = $1;
$ClientHostname = $2;
$ClientIP = $3;
}
if $Component == "cleanup" and
$Message =~ /(\w+): message-id=(<\S+@\S+>)/
{
$QueueID = $1;
$MessageID = $2;
}
if $Component == "qmgr" and
$Message =~/(\w+): from=(<\S+@\S+>), size=(\d+), nrcpt=(\w+)/
{
$QueueID = $1;
$Sender = $2;
$Size = $3;
$Nrcpt = $4;
}
if $Component == "smtp" and
$Message =~ /(?x)(\w+):\ to=(<\S+@\S+>),\ relay=([\w.]+)\[([\d.]+)\],
\ delay=(\d+),\ status=(\w+)\ \((\d+)\ \w+:\ queued\ as
\ (\w+)\)/
{
$QueueID = $1;
$Recipient = $2;
$RelayHostname = $3;
$RelayIP = $4;
$Delay = $5;
$Status = $6;
$SMTPCode = $7;
$QueueIDDelivered = $8;
}
}
</Exec>
</Input>
<Output out>
Module om_file
File "/var/log/smtp.log"
<Exec>
if $Component != "smtp" drop();
to_json();
</Exec>
</Output>
########################################
# Routes #
########################################
<Route postfix>
Path postfix => out
</Route>
Перезапускаем службу и смотрим что у нас в логе NXLog.
service nxlog restart
LogFile /var/log/nxlog/nxlog.log
А у нас не хватает прав на чтение файла /var/log/mail.log. Дадим права. Я пока тестирую, делаю совсем просто, добавляю пользователя nxlog в группу администраторов:
usermod -a -G adm nxlog
Перезапускаем службу и смотрим что у нас в логе NXLog.
service nxlog restart
Нет доступа к файлу /var/log/smtp.log. Да у нас просто нет такого файла, создадим.
cd /var/log
touch smtp.log
chown nxlog\: ./smtp.log
Перезапускаем службу.
service nxlog restart
И у нас всё заработало.
Логи Postfix считываются из файла /var/log/mail.log, преобразуются в JSON и записываются в файл /var/log/smtp.log.
Отправка логов Postfix по HTTP
Модифицируем конфигурацию для отправки логов по HTTP на адрес:
http://test.example.local/api/v1/stats/email
########################################
# Global directives #
########################################
User nxlog
Group nxlog
include /etc/nxlog/nxlog.d/*.conf
LogFile /var/log/nxlog/nxlog.log
LogLevel INFO
########################################
# Modules #
########################################
<Extension _json>
Module xm_json
</Extension>
<Input postfix>
Module im_file
File "/var/log/mail.log"
<Exec>
if $raw_event =~ /(?x)^(\S+\ +\d+\ \d+:\d+:\d+)\ (\S+)
\ postfix\/(\S+)\[(\d+)\]:\ (.+)$/
{
$EventTime = parsedate($1);
$HostName = $2;
$SourceName = "postfix";
$Component = $3;
$ProcessID = $4;
$Message = $5;
if $Component == "smtpd" and
$Message =~ /(\w+): client=(\S+)\[([\d.]+)\]/
{
$QueueID = $1;
$ClientHostname = $2;
$ClientIP = $3;
}
if $Component == "cleanup" and
$Message =~ /(\w+): message-id=(<\S+@\S+>)/
{
$QueueID = $1;
$MessageID = $2;
}
if $Component == "qmgr" and
$Message =~/(\w+): from=(<\S+@\S+>), size=(\d+), nrcpt=(\w+)/
{
$QueueID = $1;
$Sender = $2;
$Size = $3;
$Nrcpt = $4;
}
if $Component == "smtp" and
$Message =~ /(?x)(\w+):\ to=(<\S+@\S+>),\ relay=([\w.]+)\[([\d.]+)\],
\ delay=(\d+),\ status=(\w+)\ \((\d+)\ \w+:\ queued\ as
\ (\w+)\)/
{
$QueueID = $1;
$Recipient = $2;
$RelayHostname = $3;
$RelayIP = $4;
$Delay = $5;
$Status = $6;
$SMTPCode = $7;
$QueueIDDelivered = $8;
}
}
</Exec>
</Input>
<Output http>
Module om_http
URL http://test.example.local/api/v1/stats/email
ContentType text/plain
<Exec>
if $Component != "smtp" drop();
to_json();
</Exec>
</Output>
########################################
# Routes #
########################################
<Route postfix>
Path postfix => http
</Route>
P.S.
Почему-то в документации странный конфиг и регулярное выражение. Переделал регулярку под рабочий вариант:
########################################
# Global directives #
########################################
User nxlog
Group nxlog
include /etc/nxlog/nxlog.d/*.conf
LogFile /var/log/nxlog/nxlog.log
LogLevel INFO
########################################
# Modules #
########################################
<Extension _json>
Module xm_json
</Extension>
<Input postfix>
Module im_file
File "/var/log/mail.log"
<Exec>
if $raw_event =~ /(?x)^(\S+\ +\d+\ \d+:\d+:\d+)\ (\S+)
\ postfix\/(\S+)\[(\d+)\]:\ (.+)$/
{
$eventTime = parsedate($1);
$hostName = $2;
$sourceName = "postfix";
$component = $3;
$processId = $4;
$message = $5;
if $component == "smtpd" and
$message =~ /(\w+): client=(\S+)\[([\d.]+)\]/
{
$queueId = $1;
$clientHostname = $2;
$clientIp = $3;
}
if $component == "cleanup" and
$message =~ /(\w+): message-id=(<\S+@\S+>)/
{
$queueId = $1;
$messageId = $2;
}
if $component == "qmgr" and
$message =~ /(\w+): from=(<\S+@\S+>), size=(\d+), nrcpt=(\w+)/
{
$queueId = $1;
$sender = $2;
$size = $3;
$nrcpt = $4;
}
if $component == "smtp" and
$message =~ /(?x)(\w+):\ [host]*\ ?([\w.\ -]*)?\[?[\d.]+\]?\ ?(\w*:?\ ?([\d.\ -]*)?\ (.+)?)/
{
$queueId = $1;
$relayHostname = $2;
$smtpCode = $4;
if not defined($4)
{
$smtpInfo = $3;
} else {
$smtpInfo = $5;
}
}
if $component == "smtp" and
$message =~ /(?x)(\w+):\ to=<(\S+@\S+)>,\ relay=([\w.-]+)\[?([\d.]*)\]?:?(\d*),\ delay=([\d.]+),\ delays=[\d.]+\/[\d.]+\/[\d.]+\/[\d.]+,\ dsn=[\d.]+,\ status=(\w+)\ \(((host|connect)?\ ?[\w.\ -]*\[?[\d.]*\]?[\w\ ]*:\ ?)?([\d.\ -]*)\ (.+)\)/
{
$queueId = $1;
$recipient = $2;
$relayHostname = $3;
$relayIp = $4;
$relayPort = $5;
$delay = $6;
$status = $7;
$smtpCode = $10;
$smtpInfo = $11;
}
}
</Exec>
</Input>
#<Output out>
# Module om_file
# File "/var/log/smtp.log"
# <Exec>
# if $component != "smtp" drop();
# #if $relayHostname != "mail.example.com" drop();
# to_json();
# </Exec>
#</Output>
<Output http>
Module om_http
URL http://test.example.local/api/v1/stats/email
ContentType text/plain
<Exec>
if $component != "smtp" drop();
to_json();
</Exec>
</Output>
########################################
# Routes #
########################################
<Route postfix>
Path postfix => http
</Route>
#<Route postfix>
# Path postfix => out
#</Route>