创建一个全天候运行并从命名管道读取数据的Perl守护程序

hec6srdp  于 2022-11-15  发布在  Perl
关注(0)|答案(2)|浏览(200)

我正在尝试使用perl创建一个日志分析器。该分析器将在AIX服务器上的后台24/7运行,并从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中创建一个.conf文件来保存信息。类似于:

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

因此,从A获得的任何内容都将通过邮件发送到root@domain.com,而从B获得的所有内容都将保存到日志文件(就像通常一样),并且将发送到user@domain.com
鉴于这是我第一次使用Perl,也是我第一次创建守护进程,在遵循KISS原则的情况下,我有什么办法可以做这个吗?还有,我应该遵守什么约定吗?如果你能考虑到我在回答时缺乏知识,那将是最有帮助的。

igsr9ssn

igsr9ssn1#

我将回答您的部分问题:如何编写处理IO的长时间运行的Perl程序。
编写处理多个并发IO操作的Perl程序的最有效方法是使用事件循环。这将允许我们编写事件处理程序,如“命名管道上出现一行”或“电子邮件发送成功”或“我们收到SIGINT”。关键是,它将允许我们在一个程序中组合任意数量的事件处理程序。2这意味着你可以“多任务处理”但仍然可以很容易地在任务之间共享状态。
我们将使用AnyEvent框架。它允许我们编写事件处理程序,称为watchers,它将与Perl支持的任何事件循环一起工作。您可能不关心您使用的是哪个事件循环,因此这种抽象可能对您的应用程序没有影响。但它将允许我们重用CPAN上可用的预先编写的事件处理程序; AnyEvent::SMTP处理电子邮件,AnyEvent::Subprocess与子进程交互,AnyEvent::Handle处理管道,等等。
基于AnyEvent的守护进程的基本结构非常简单,您可以创建一些监视器,进入事件循环,然后......就这样;事件系统完成所有其他工作。首先,让我们编写一个每五秒打印一次“Hello”的程序。
我们从加载模块开始:

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

然后,我们将创建一个时间观察器,或“计时器”:

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

注意,我们将定时器赋给一个变量,这样只要$t在作用域中,定时器就一直处于活动状态。如果我们使用undef $t,那么定时器将被取消,回调将永远不会被调用。
关于回调,这是cb =>之后的sub { ... },这就是我们处理事件的方式。当一个事件发生时,回调被调用。我们做我们的事情,返回,事件循环继续根据需要调用其他回调。你可以在回调中做任何你想做的事情,包括取消和创建其他观察器。只是不要进行阻塞调用。如system("/bin/sh long running process")my $line = <$fh>sleep 10。任何阻塞都必须由监视器完成;否则,在等待该任务完成时,事件循环将无法运行其他处理程序。
现在我们已经有了一个计时器,我们只需要进入事件循环。通常,你会选择一个你想要使用的事件循环,并以事件循环文档描述的特定方式输入它。EV是一个很好的方式,你可以通过调用EV::loop()来输入它。但是,我们将让AnyEvent来决定使用什么事件循环。写AnyEvent->condvar->recv.不要担心它的作用它是一个成语,意思是“进入事件循环,永不返回”。(在阅读AnyEvent时,您会看到很多关于条件变量(condvar)的内容。在文档和单元测试中,它们很适合用作示例,但您真的不希望在程序中使用它们。如果您在.pm文件中使用它们,所以现在就假装它们不存在,这样从一开始你就能写出非常干净的代码,这会让你领先于许多CPAN作者!)
因此,为了完整起见:

AnyEvent->condvar->recv;

如果你运行这个程序,它会每5秒打印一次“Hello”,直到宇宙结束,或者,更有可能的是,你用控件c杀死它。这个程序的巧妙之处在于,你可以在打印“Hello”之间的5秒内做其他事情,而你只需添加更多的观察者。
现在,我们来看看从管道阅读数据。AnyEvent通过它的AnyEvent::Handle模块使这一点变得非常容易。AnyEvent::Handle可以连接到套接字或管道,并且在数据可供读取时调用回调函数。(它还可以执行非阻塞写入、TLS和其他操作。但我们现在不关心这些。)
首先,我们需要打开一个管道:

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

然后,我们用一个AnyEvent::Handle Package 它。在创建Handle对象之后,我们将使用它来执行此管道上的所有操作。您可以完全忘记$fh,AnyEvent::Handle将直接处理它。

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

现在,我们可以使用$h从管道中读取可用的行:

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

这将调用一个回调函数,在下一行可用时输出“Got a 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。(您不必在行级别读取;您可以要求在任何字节可用时都调用回调函数(但这比较复杂,留给读者作为练习)。

现在我们可以将这些连接到一个守护进程中,它负责阅读管道。我们要做的是:为行创建一个处理程序,打开管道并处理行,最后设置一个信号处理程序以干净地退出程序。执行每个操作(“handle lines from the access log file”)一个带有startstop方法的类,示例化一组操作,设置一个信号处理程序来干净地停止这些操作,启动所有操作,然后进入事件循环。这是很多与此问题实际上无关的代码,所以我们会做一些简单的事情。但是在你设计程序的时候要记住这一点。

#!/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时干净地退出!

lf5gs5x2

lf5gs5x22#

第一个简化--在一个单独的进程中处理每个命名管道,这意味着您将为每个命名管道运行一个perl进程,但这样您就不必使用基于事件的I/O或线程。
鉴于此,在命令行上传递配置数据(命名管道的路径、要使用的电子邮件地址等)如何,例如:

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

这样行吗?
为了确保守护进程保持运行,请查看像D. J. Bernstein's daemontoolssupervisord这样的包(哎呀!一个python包)。
这些包中的每一个都告诉你如何配置你的rc-scripts,使它们在机器 Boot 时启动。

相关问题