制作一个 24/7 运行并从命名管道读取的 Perl 守护进程

发布于 2024-11-26 15:06:03 字数 1610 浏览 8 评论 0原文

我正在尝试使用 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 [email protected] 写入的所有日志命名管道C。为此,我假设守护进程需要有一个散列(对于 perl 来说是新的,但这似乎是一个合适的数据结构),它可以动态更改并告诉它如何处理每个管道。

这可能吗?或者我应该在 /etc 中创建一个 .conf 文件来保存信息。像这样的事情:

namedpipeA:'mail [email protected]'
namedpipeB:save:'mail [email protected]'

因此,从 A 获取任何内容都将邮寄到 [email protected]B 中的所有内容都将保存到日志文件中(就像通常一样)并且它将被发送到 [email protected]< /a>

这是我第一次使用 Perl 也是我第一次创建守护进程,有吗无论如何,我要在遵守 KISS 原则的同时做到这一点?另外,有什么我应该遵守的约定吗?如果您在回复时能考虑到我缺乏知识,那将是最有帮助的。

I'm trying to make a log analyser using perl. The analyser would run 24/7 in the background on an AIX server and read from pipes that syslog directs logs to (from the entire network). Basically:

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

So, for example, I want my daemon to be able to be configured to mail [email protected] all logs that are written to named pipe C. For this, I'm assuming the daemon needs to have a hash (new to perl, but this seems like an appropriate data structure) that would be able to be changed on the fly and would tell it what to do with each pipe.

Is this possible? Or should I create a .conf file in /etc to hold the information. Something like this:

namedpipeA:'mail [email protected]'
namedpipeB:save:'mail [email protected]'

So getting anything from A will be mailed to [email protected] and everything from B will be saved to a log file (like it is usually) AND it will be sent to [email protected]

Seeing as this is my first time using Perl and my first time creating a daemon, is there anyway for me to make this while adhering to the KISS principal? Also, are there any conventions that I should stick to? If you could take into consideration my lack of knowledge when replying it would be most helpful.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

冷︶言冷语的世界 2024-12-03 15:06:03

我将回答你的部分问题:如何编写一个长期运行的处理 IO 的 Perl 程序。

编写处理许多并发 IO 操作的 Perl 程序的最有效方法是使用事件循环。这将允许我们为事件编写处理程序,例如“命名管道上出现一行”或“电子邮件已成功发送”或“我们收到了 SIGINT”。至关重要的是,它将允许我们在一个程序中组合任意数量的这些事件处理程序。这意味着您可以“执行多任务”,但仍然可以轻松地在任务之间共享状态。

我们将使用 AnyEvent 框架。它允许我们编写事件处理程序,称为观察程序,它将与 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>睡 10 点。任何阻塞的事情都必须由观察者来完成;否则,事件循环在等待该任务完成时将无法运行其他处理程序。

现在我们有了一个计时器,我们只需要进入事件循环即可。通常,您将选择要使用的事件循环,并按照事件循环文档描述的特定方式输入它。 EV 是一个不错的选择,您可以通过调用 EV::loop() 输入它。但是,我们将通过编写 AnyEvent->condvar->recv 让 AnyEvent 决定使用哪个事件循环。不要担心这会做什么;这是一个习语,意思是“进入事件循环并且永不返回”。 (当您阅读 AnyEvent 时,您会看到很多有关条件变量或 condvar 的内容。它们对于文档和单元测试中的示例非常有用,但您真的不想在程序中使用它们。如果您在 .pm 文件中使用它们,您正在做一些非常错误的事情,所以只要假装它们现在不存在,您就会从一开始就编写出非常干净的代码。这将使您领先于许多 CPAN 作者!)

因此,为了完整起见:

AnyEvent->condvar->recv;

如果您运行该程序,它将每五秒打印一次“Hello”,直到宇宙结束,或者更有可能的是,您使用控制 c 杀死它。这样做的巧妙之处在于,您可以在打印“Hello”之间的五秒钟内做其他事情,并且只需添加更多观察者即可做到这一点。

那么,现在开始从管道中读取数据。 AnyEvent 通过其 AnyEvent::Handle 模块使这变得非常容易。 AnyEvent::Handle 可以连接到套接字或管道,并且只要有数据可供读取,就会调用回调。 (它还可以执行非阻塞写入、TLS 和其他操作。但我们现在不关心这些。)

首先,我们需要打开一个管道:

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

然后,我们用 AnyEvent::Handle 包装它。创建 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 即可。 (您不必在行级别阅读;您可以要求在任何字节可用时调用回调。但这更复杂,留给读者作为练习。)

所以现在我们可以将这一切结合在一起处理读取管道的守护进程。我们想要做的是:为线路创建一个处理程序,打开管道并处理线路,最后设置一个信号处理程序以干净地退出程序。我建议采用面向对象的方法来解决这个问题;使每个操作(“处理访问日志文件中的行”)成为一个具有 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 时干净地退出!

I'll cover part of your question: how to write a long-running Perl program that deals with IO.

The most efficient way to write a Perl program that handles many simultaneous IO operations is to use an event loop. This will allow us to write handlers for events, like "a line appeared on the named pipe" or "the email was sent successfully" or "we received SIGINT". Crucially, it will allow us to compose an arbitrary number of these event handlers in one program. This means that you can "multitask" but still easily share state between the tasks.

We'll use the AnyEvent framework. It lets us write event handlers, called watchers, that will work with any event loop that Perl supports. You probably don't care which event loop you use, so this abstraction probably doesn't matter to your application. But it will let us reuse pre-written event handlers available on CPAN; AnyEvent::SMTP to handle email, AnyEvent::Subprocess to interact with child processes, AnyEvent::Handle to deal with the pipes, and so on.

The basic structure of an AnyEvent-based daemon is very simple. You create some watchers, enter the event loop, and ... that's it; the event system does everything else. To get started, let's write a program that will print "Hello" every five seconds.

We start by loading modules:

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

Then, we'll create a time watcher, or a "timer":

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

Note that we assign the timer to a variable. This keeps the timer alive as long as $t is in scope. If we said undef $t, then the timer would be cancelled and the callback would never be called.

About callbacks, that's the sub { ... } after cb =>, and that's how we handle events. When an event happens, the callback is invoked. We do our thing, return, and the event loop continues calling other callbacks as necessary. You can do anything you want in callbacks, including cancelling and creating other watchers. Just don't make a blocking call, like system("/bin/sh long running process") or my $line = <$fh> or sleep 10. Anything that blocks must be done by a watcher; otherwise, the event loop won't be able to run other handlers while waiting for that task to complete.

Now that we have a timer, we just need to enter the event loop. Typically, you'll choose an event loop that you want to use, and enter it in the specific way that the event loop's documentation describes. EV is a good one, and you enter it by calling EV::loop(). But, we'll let AnyEvent make the decision about what event loop to use, by writing AnyEvent->condvar->recv. Don't worry what this does; it's an idiom that means "enter the event loop and never return". (You'll see a lot about condition variables, or condvars, as you read about AnyEvent. They are nice for examples in the documentation and in unit tests, but you really don't want to ever use them in your program. If you're using them inside a .pm file, you're doing something very wrong. So just pretend they don't exist for now, and you'll write extremely clean code right from the start. And that'll put you ahead of many CPAN authors!)

So, just for completeness:

AnyEvent->condvar->recv;

If you run that program, it will print "Hello" every five seconds until the universe ends, or, more likely, you kill it with control c. What's neat about this is that you can do other things in those five seconds between printing "Hello", and you do it just by adding more watchers.

So, now onto reading from pipes. AnyEvent makes this very easy with its AnyEvent::Handle module. AnyEvent::Handle can connect to sockets or pipes and will call a callback whenever data is available to read from them. (It can also do non-blocking writes, TLS, and other stuff. But we don't care about that right now.)

First, we need to open a pipe:

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

Then, we wrap it with an AnyEvent::Handle. After creating the Handle object, we'll use it for all operations on this pipe. You can completely forget about $fh, AnyEvent::Handle will handle touching it directly.

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

Now we can use $h to read lines from the pipe when they become available:

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

This will call the callback that prints "Got a line" when the next line becomes available. If you want to continue reading lines, then you need to make the function push itself back onto the read queue, like:

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 );

This will read lines and call $handle_line->() for each line until the file is closed. If you want to stop reading early, that's easy... just don't push_read again in that case. (You don't have to read at the line level; you can ask that your callback be called whenever any bytes become available. But that's more complicated and left as an exercise to the reader.)

So now we can tie this all together into a daemon that handles reading the pipes. What we want to do is: create a handler for lines, open the pipes and handle the lines, and finally set up a signal handler to cleanly exit the program. I recommend taking an OO approach to this problem; make each action ("handle lines from the access log file") a class with a start and stop method, instantiate a bunch of actions, setup a signal handler to cleanly stop the actions, start all the actions, and then enter the event loop. That's a lot of code that's not really related to this problem, so we'll do something simpler. But keep that in mind as you design your program.

#!/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;

Now you have a program that reads a line from an arbitrary number of pipes, prints each line received on any pipe (prefixed with the path to the pipe), and exits cleanly when you press Control-C!

莫多说 2024-12-03 15:06:03

第一个简化 - 在单独的进程中处理每个命名管道。这意味着您将为每个命名管道运行一个 perl 进程,但您不必使用基于事件的 I/O 或线程。

鉴于此,如何在命令行上传递配置数据(命名管道的路径、要使用的电子邮件地址等),例如:

the-daemon --pipe /path/to/named-pipe-A --mailto [email protected]
the-daemon --pipe /path/to/named-pipe-B --mailto [email protected]
...

这对您有用吗?

为了确保守护进程保持运行,请查看 DJ Bernstein 的 daemontools 或 < a href="http://supervisord.org/" rel="nofollow noreferrer">supervisord (哎呀!一个Python包)。

每个软件包都会告诉您如何配置 rc 脚本,以便它们在计算机启动时启动。

First simplification - handle each named pipe in a separate process. That means you will run one perl process for each named pipe, but then you won't have to use event-based I/O or threading.

Given that, how about just passing the configuration data (path to the named pipe, email address to use, etc.) on the command line, e.g.:

the-daemon --pipe /path/to/named-pipe-A --mailto [email protected]
the-daemon --pipe /path/to/named-pipe-B --mailto [email protected]
...

Does that work for you?

To make sure the daemons stay up, have a look at a package like D. J. Bernstein's daemontools or supervisord (gasp! a python package).

Each of these packages tells you how to configure your rc-scripts so that they start at machine boot time.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文