Создание демона Perl, который работает 24/7 и читает из именованных каналов

Я пытаюсь сделать анализатор журнала с помощью perl. Анализатор будет работать 24/7 в фоновом режиме на сервере AIX и считывать из каналов, которые syslog направляет журналы (из всей сети). В основном:

logs from network ----> named pipe A -------->   | perl daemon
                  ----> named pipe B -------->   | * reads pipes
                  ----> named pipe c -------->   | * decides what to do based on which pipe

так, например, я хочу, чтобы мой демон мог быть настроен на mail root@domain.com все журналы, которые записываются в named pipe C. Для этого я предполагаю, что демон должен иметь хэш (новый для perl, но это похоже на соответствующую структуру данных), который мог бы быть менялся на лету и говорил бы ему, что делать с каждой трубой.

возможно ли это? Или я должен создать на /etc для хранения информации. Что-то вроде этого:--11-->

namedpipeA:'mail root@domain.com'
namedpipeB:save:'mail user@domain.com'

так получать что-нибудь от A будут высланы root@domain.com и все от B будет сохранен в файл журнала (как обычно), и он будет отправлен в user@domain.com

видя, что это мой первый раз, используя Perl и мой первый раз, создавая демона, есть в любом случае для меня, чтобы сделать это, придерживаясь поцелуй основные? Кроме того, есть ли какие-либо соглашения, которые я должен придерживаться? Если бы вы могли принять во внимание мое отсутствие знаний при ответе, это было бы очень полезно.

2 ответов


я рассмотрю часть вашего вопроса: Как написать долгосрочную программу Perl, которая имеет дело с IO.

самый эффективный способ написать программу Perl, которая обрабатывает многие одновременные операции ввода-вывода, - использовать цикл событий. Это позволит нам писать обработчики для событий, таких как" строка появилась на именованном канале "или" письмо было отправлено успешно "или"мы получили SIGINT". Важно, что это позволит нам compose произвольное число этих обработчиков событий в одной программе. Это означает, что вы можете "многозадачность", но по-прежнему легко разделить состояние между задачами.

мы будем использовать AnyEvent основы. Он позволяет нам писать обработчики событий, называемые наблюдателями, которые будут работать с любым циклом событий, поддерживаемым Perl. Вероятно, вам все равно, какой цикл событий вы используете, поэтому эта абстракция, вероятно, не имеет значения для вашего приложения. Но это позволит нам повторно использовать предварительно написанные обработчики событий, доступные на CPAN;AnyEvent:: SMTP для обработки электронной почты, AnyEvent::Подпроцесс для взаимодействия с дочерними процессами, AnyEvent:: Handle иметь дело с трубами, и так далее.

базовая структура демона на основе AnyEvent очень проста. Вы создаете несколько наблюдателей, вводите цикл событий и ... в системе событий делает все остальное. Для начала давайте напишем программу, которая будет печатать "Hello" каждые пять секунд.

мы начинаем с загрузки модули:

use strict;
use warnings;
use 5.010;
use AnyEvent;

затем мы создадим Time watcher или "таймер":

my $t = AnyEvent->timer( after => 0, interval => 5, cb => sub {
    say "Hello";
});

обратите внимание, что мы назначаем таймер переменной. Это держит таймер живым до тех пор, пока $t в области. Если бы мы сказали undef $t, то таймер будет отменен и обратный вызов никогда не будет называться.

о обратных вызовах, это sub { ... } после cb =>, и именно так мы обрабатываем события. Когда происходит событие, вызывается обратный вызов. Мы делаем свое дело, возврат, и цикл событий продолжает вызывать другие обратные вызовы по мере необходимости. Вы можете делать все, что хотите, в обратных вызовах, включая отмену и создание других наблюдателей. Просто не делайте блокирующий вызов, как system("/bin/sh long running process") или my $line = <$fh> или sleep 10. Все, что блокирует, должно выполняться наблюдателем; в противном случае цикл событий не сможет запускать другие обработчики, ожидая завершения этой задачи.

теперь у нас есть таймер, нам просто нужно войти в цикл обработки событий. Как правило, вы выберите цикл событий, который вы хотите использовать, и введите его определенным образом, описанным в документации цикла событий. EV хороший, и вы входите в него, вызывая EV::loop(). Но мы позволим AnyEvent принять решение о том, какой цикл событий использовать, написав AnyEvent->condvar->recv. Не волнуйтесь, что это делает; это идиома, которая означает "войдите в цикл событий и никогда не возвращайтесь". (Вы увидите много о переменных условий или кондварах, как Вы читаете о AnyEvent. Они хороши для примеры в документации и модульных тестах, но вы действительно не хотите использовать их в своей программе. Если вы используете их внутри , вы делаете что-то очень неправильно. Поэтому просто притворитесь, что их пока нет, и вы напишете очень чистый код с самого начала. И это поставит вас впереди многих авторов CPAN!)

так, просто для полноты:

AnyEvent->condvar->recv;

если вы запустите эту программу, она будет печатать "Hello" каждые пять секунд, пока Вселенная заканчивается, или, что более вероятно, вы убиваете ее с помощью control c. Что аккуратно в этом, так это то, что вы можете делать другие вещи за эти пять секунд между печатью "Hello", и вы делаете это, просто добавляя больше наблюдателей.

Итак, теперь о чтении из труб. AnyEvent делает это очень легко с его модулем AnyEvent:: Handle. AnyEvent:: Handle может подключаться к сокетам или трубам и вызывать обратный вызов, когда данные доступны для чтения из них. (Он также может делать неблокирующие записи, TLS и другие материал. Но сейчас нас это не волнует.)

во-первых, нам нужно открыть трубу:

use autodie 'open';
open my $fh, '<', '/path/to/pipe';

затем мы обернем его ручкой AnyEvent::. После создания объекта дескриптора мы будем использовать его для всех операций над этим каналом. Вы можете полностью забыть о $fh, AnyEvent:: ручка будет обрабатывать прикосновение непосредственно.

my $h = AnyEvent::Handle->new( fh => $fh );

теперь мы можем использовать $h читать строки из трубы, когда они становятся доступными:

$h->push_read( line => sub {
    my ($h, $line, $eol) = @_;
    say "Got a line: $line";
});

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

my $handle_line; $handle_line = sub {
    my ($h, $line, $eol) = @_;
    say "Got a line: $line";
    $h->push_read( line => $handle_line );
};
$h->push_read( line => $handle_line );

это будет читать строки и вызов $handle_line->() для каждой строки, пока файл не будет закрыт. Если вы хотите прекратить читать пораньше, это легко... просто не push_read снова в этом случае. (Вам не нужно читать на уровне строки; вы можете попросить, чтобы ваш обратный вызов был вызван всякий раз, когда байты становятся доступными. Но это сложнее и оставлено как упражнение для читателя.)

Итак, теперь мы можем связать все это вместе в демона, который обрабатывает чтение труб. Что мы хотим сделать: создать обработчик для линий, открыть трубы и обработать линии и, наконец, настроить обработчик сигналов для чистого выхода из программы. Я рекомендую использовать подход OO к этой проблеме; Сделайте каждое действие ("обрабатывать строки из файла журнала доступа") классом с start и stop метод, создать экземпляр группы действий, настроить обработчик сигнала, чтобы чисто остановить действия, запустить все действия, а затем введите цикл событий. Это много кода, который на самом деле не связан с этой проблемой, поэтому мы сделаем что-то проще. Но помните об этом при разработке программы.

#!/usr/bin/env perl
use strict;
use warnings;
use AnyEvent;
use AnyEvent::Handle;
use EV;

use autodie 'open';
use 5.010;

my @handles;

my $abort; $abort = AnyEvent->signal( signal => 'INT', cb => sub {
    say "Exiting.";
    $_->destroy for @handles;
    undef $abort; 
    # all watchers destroyed, event loop will return
});

my $handler; $handler = sub {
    my ($h, $line, $eol) = @_;
    my $name = $h->{name};
    say "$name: $line";
    $h->push_read( line => $handler );
};

for my $file (@ARGV) {
    open my $fh, '<', $file;
    my $h = AnyEvent::Handle->new( fh => $fh );
    $h->{name} = $file;
    $h->push_read( line => $handler );
}

EV::loop;

теперь у вас есть программа, которая считывает строку из произвольного числа труб, печатает каждую строку, полученную на любой трубе (с префиксом пути к трубе), и выходит чисто, когда вы нажимаете Control-C!


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

учитывая это, как насчет просто передачи данных конфигурации (путь к именованному каналу, адрес электронной почты для использования и т. д.) в командной строке, например:

the-daemon --pipe /path/to/named-pipe-A --mailto root@domainA.com
the-daemon --pipe /path/to/named-pipe-B --mailto root@domainB.com
...

это работает для вас?

чтобы убедиться, что демоны остаются, посмотрите на пакет, как демоны Д. Дж. Бернштейна или supervisord (гпбп! пакет python).

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