Creating a Perl daemon that runs 24/7 and reads from named pipes

I am trying to make a log analyzer using perl. The analyzer will run 24/7 in the background on the AIX server and read from the channels that syslog sends logs to (from the entire network). Mostly:

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 configured to mail root@domain.com all the logs recorded in named pipe C To do this, I assume that the daemon should have a hash (new to perl, but it looks like an appropriate data structure) that could be changed on the fly and tell you what to do with each channel.

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

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

Thus, the receipt of something from A will be sent by email to root@domain.com , and all of B will be saved to a log file (as usual) And it will be sent to user@domain.com

Seeing that this is my first time when I use Perl, and when I first created the daemon, do I still need to do this, adhering to the KISS principle? Also, are there any agreements that I must adhere to? If you could take into account my lack of knowledge when the answer would be useful.

+7
source share
2 answers

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

The most efficient way to write a Perl program that handles many simultaneous I / O operations is to use an event loop. This will allow us to write event handlers, for example, "the string indicated on the named pipe" or "the message was sent successfully" or "we received SIGINT". Actually, this will allow us to compose an arbitrary number of these event handlers in one program. This means that you can "multitask", but it’s still easy to share state between tasks.

We will use AnyEvent . This allows us to write event handlers called observers 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 for your application. But this will allow us to reuse pre-written event handlers available on CPAN; AnyEvent :: SMTP for handling email AnyEvent :: Subprocess for interacting with AnyEvent :: Handle child processes for processing pipes, etc.

The basic structure of the AnyEvent daemon is very simple. You create observers, enter an event loop and ... what is it; an event system does the rest. To get started, write a program that will print “Hello” every five seconds.

Let's start by loading the modules:

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

Then we will create a time observer or "timer":

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

Note that we assign a timer to a variable. This allows you to keep the timer as long as $t is in scope. If we said undef $t , then the timer will be canceled and the callback will never be called.

About callbacks that sub { ... } after cb => , and how we handle events. When an event occurs, a callback is called. We do our business, return, and the event loop continues to call other callbacks as needed. You can do whatever you want in callbacks, including undoing and creating other observers. Just don't make a blocking call, such as system("/bin/sh long running process") or my $line = <$fh> or sleep 10 . Everything that is blocked must be done by an observer; otherwise, the event loop will not be able to fire other handlers while waiting for this task to complete.

Now that we have a timer, we just need to enter the event loop. Typically, you select the event loop that you want to use and enter it as described in the event loop documentation. EV is good, and you enter it by calling EV::loop() . But we will let AnyEvent decide which event loop to use by writing AnyEvent->condvar->recv . Do not worry what it does; it’s an idiom that means “enter the cycle of events and never return.” (You will see a lot about condition variables or condvars as you read about AnyEvent. They are good for examples in documentation and unit tests, but you really don't want to use them in your program. " .pm using them inside the .pm file, you do something very wrong. Therefore, just pretend that they do not exist at the moment, and you will write very clean code from the very beginning. And this will put you ahead of many CPAN authors!)

So, just for completeness:

 AnyEvent->condvar->recv; 

If you run this program, it will print “Hello” every five seconds until the universe ends, or, more likely, you destroy it with c. How neat is that you can do other things in the five seconds between printing “Hello,” and you do this by simply adding more watchers.

So, now for reading from the pipes. AnyEvent makes this very easy with the AnyEvent :: Handle module. AnyEvent :: Handle can connect to sockets or pipes and will call a callback when data is available for reading. (He can also make non-blocking entries, TLS, etc. But we don’t care about that now.)

First we need to open the channel:

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

Then we end it with AnyEvent :: Handle. After creating the Handle object, we will use it for all operations on this channel. You can completely forget about $fh , AnyEvent :: Handle will handle it directly.

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

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

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

This will trigger a callback that prints “Got a line” when the next line becomes available. If you want to continue reading lines, you need to force the function to push itself back into the read queue, for example:

 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 the lines and call $handle_line->() for each line until the file is closed. If you want to stop reading early, it's just ... just don't do push_read again in this case. (You do not need to read at the linear level, you can ask your callback to be called whenever any bytes appear, but it was more complex and remained as an exercise for the reader.)

So, now we can bind all this together with the daemon that processes the channels. We want to do this: create a handler for strings, open channels and process strings, and finally configure a signal handler for a clean exit from the program. I recommend using the OO approach to this problem; make each action ("process lines from the access log file") a class using the start and stop method, create an instance of the sequence of actions, configure the signal handler to completely stop the actions, start all actions, and then enter the event loop. This is a lot of code that is really not related to this problem, so we will do something simpler. But keep this in mind when 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 channel (with a channel path prefix), and automatically exits when you press Control-C!

+18
source

The first simplification is to process each named pipe in a separate process. This means that you will run one perl process for each named pipe, but then you do not have to use I / O events or threads.

Given this, it's easy to pass configuration data (path to a named pipe, email address to use, etc.) on the command line, for example:

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

Does this work for you?

To make sure that the daemons do not get up, look at the package, for example DJ Bernstein daemontools or supervisord (gasp! Python package).

Each of these packages tells you how to configure your rc scripts so that they run when the machine boots.

+2
source

All Articles