File Coverage

blib/lib/Net/Peep/Client/Logparser.pm
Criterion Covered Total %
statement 81 212 38.2
branch 4 52 7.6
condition 8 48 16.6
subroutine 18 27 66.6
pod 0 10 0.0
total 111 349 31.8


line stmt bran cond sub pod time code
1             package Net::Peep::Client::Logparser;
2              
3             require 5.00503;
4 1     1   875 use strict;
  1         3  
  1         42  
5             # use warnings; # commented out for 5.005 compatibility
6 1     1   7 use Carp;
  1         2  
  1         67  
7 1     1   6 use Data::Dumper;
  1         2  
  1         44  
8 1     1   932 use File::Tail;
  1         32062  
  1         58  
9 1     1   12 use Sys::Hostname;
  1         2  
  1         62  
10 1     1   752 use Net::Peep::Client;
  1         4  
  1         73  
11 1     1   798 use Net::Peep::Client::Logparser::Event;
  1         4  
  1         48  
12 1     1   4 use Net::Peep::BC;
  1         2  
  1         31  
13 1     1   5 use Net::Peep::Notifier;
  1         1  
  1         31  
14 1     1   572 use Net::Peep::Notification;
  1         3  
  1         51  
15              
16             require Exporter;
17              
18 1     1   6 use vars qw{ @ISA %EXPORT_TAGS @EXPORT_OK @EXPORT $VERSION };
  1         1  
  1         149  
19              
20             @ISA = qw(Exporter Net::Peep::Client);
21             %EXPORT_TAGS = ( 'all' => [ qw( INTERVAL MAX_INTERVAL ADJUST_AFTER ) ] );
22             @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
23             @EXPORT = qw( );
24             $VERSION = do { my @r = (q$Revision: 1.11 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r };
25              
26             # These are in seconds and are the parameters for File::Tail
27              
28             # File Tail uses the idea of intervals and predictions to try to keep
29             # blocking time at a maximum. These three parameters are the ones that
30             # people will want to tune for performance vs. load. The smaller the
31             # interval, the higher the load but faster events are picked up.
32              
33             # The interval that File::Tail waits before checking the log
34 1     1   6 use constant INTERVAL => 0.1;
  1         2  
  1         71  
35             # The maximum interval that File::Tail will wait before checking the
36             # log
37 1     1   14 use constant MAX_INTERVAL => 1;
  1         3  
  1         53  
38             # The time after which File::Tail adjusts its predictions
39 1     1   6 use constant ADJUST_AFTER => 2;
  1         2  
  1         103  
40              
41 1     1   6 use constant DEFAULT_PID_FILE => "/var/run/logparser.pid";
  1         74  
  1         4114  
42              
43             sub new {
44              
45 1     1 0 281 my $self = shift;
46 1   33     9 my $class = ref($self) || $self;
47 1         15 my $this = $class->SUPER::new();
48 1         4 bless $this, $class;
49 1         10 $this->{'EVENTS'} = [];
50 1         11 $this->name('logparser');
51 1         4 $this;
52              
53             } # end sub new
54              
55             sub getLogFiles {
56              
57 0     0 0 0 my $self = shift;
58 0         0 my $conf = $self->conf();
59 0 0       0 my $logfiles = $conf->optionExists('logfile') ? $conf->getOption('logfile') : '';
60 0         0 my @logfiles = split ',\s*', $logfiles;
61 0 0       0 return wantarray ? @logfiles : [@logfiles];
62              
63             } # sub getLogFiles
64              
65             sub getLogFileTails {
66              
67 0     0 0 0 my $self = shift;
68 0 0       0 if ( ! exists $self->{"__LOGFILETAILS"} ) {
69              
70 0         0 my @logfiles = $self->getLogFiles();
71 0         0 my @tailfiles;
72 0         0 for my $logfile (@logfiles) {
73 0 0       0 if (-e $logfile) {
74 0         0 my $tail;
75 0         0 eval { $tail =
  0         0  
76             File::Tail->new(
77             name => $logfile,
78             interval => INTERVAL,
79             maxinterval => MAX_INTERVAL,
80             adjustafter => ADJUST_AFTER
81             );
82             };
83 0 0       0 if ($@) {
84 0         0 chomp $@;
85 0         0 $self->logger()->log("Warning: Error creating tail of logfile '$logfile': $@");
86             } else {
87 0         0 push @tailfiles, $tail;
88             }
89             } else {
90 0         0 $self->logger()->log("Warning: Can't tail the log file '$logfile': It doesn't exist.");
91             }
92             }
93              
94 0         0 $self->{"__LOGFILETAILS"} = \@tailfiles;
95              
96             }
97              
98 0 0       0 return wantarray ? @{$self->{"__LOGFILETAILS"}} : $self->{"__LOGFILETAILS"};
  0         0  
99              
100             } # sub getLogFileTails
101              
102             sub Start {
103              
104 0     0 0 0 my $self = shift;
105              
106 0         0 my $events = '';
107 0         0 my $logfile = '';
108 0         0 my $pidfile = DEFAULT_PID_FILE;
109 0         0 my @groups = ();
110 0         0 my @exclude = ();
111              
112 0         0 my %options = (
113             'events=s' => \$events,
114             'logfile=s' => \$logfile,
115             'pidfile=s' => \$pidfile,
116             'groups=s' => \@groups,
117             'exclude=s' => \@exclude );
118              
119 0 0       0 $self->initialize(%options) || $self->pods();
120              
121             # register a parser for the logparser section of the configuration file
122              
123 0         0 $self->logger()->debug(9,"Registering parser ...");
124 0     0   0 $self->parser(sub { my @text = @_; $self->parse(@text); });
  0         0  
  0         0  
125 0         0 $self->logger()->debug(9,"\tParser registered ...");
126              
127             # have the client parse the configuration file and
128             # get the configuration object which should be populated with the
129             # standard command-line options and configuration information
130 0         0 my $conf = $self->configure();
131              
132 0 0       0 unless ($conf->getOption('autodiscovery')) {
133 0 0 0     0 $self->pods("Error: Without autodiscovery you must provide a server and port option.")
      0        
      0        
134             unless $conf->optionExists('server') && $conf->optionExists('port') &&
135             $conf->getOption('server') && $conf->getOption('port');
136             }
137              
138 0         0 my @gotgroups = $self->getGroups();
139 0         0 my @gotexclude = $self->getExcluded();
140 0         0 $self->logger()->debug(1,"Recognized event groups are [@gotgroups]");
141 0         0 $self->logger()->debug(1,"Excluded event groups are [@gotexclude]");
142              
143             # Check whether the pidfile option was set. If not, use the default
144 0 0       0 unless ($conf->optionExists('pidfile')) {
145 0         0 $self->logger()->debug(3,"No pid file specified. Using default [" . DEFAULT_PID_FILE . "]");
146 0         0 $conf->setOption('pidfile', DEFAULT_PID_FILE);
147             }
148              
149 0         0 $self->logger()->log("Scanning logs:");
150              
151 0         0 for my $logfile ($self->getLogFiles()) {
152 0         0 $self->logger()->log("\t$logfile");
153             }
154              
155             # Register a callback for the main loop
156 0         0 $self->logger()->debug(9,"Registering callback ...");
157 0     0   0 $self->callback(sub { $self->loop(); });
  0         0  
158 0         0 $self->logger()->debug(9,"\tCallback registered ...");
159              
160 0         0 $self->MainLoop();
161              
162 0         0 return 1;
163              
164             } # end sub Start
165              
166             sub loop {
167              
168 0     0 0 0 my $self = shift;
169              
170 0         0 select(STDOUT);
171              
172 0         0 $| = 1; # autoflush
173              
174 0         0 my $nfound;
175              
176 0         0 my @logFileTails = $self->getLogFileTails();
177              
178             # call the peck method which, the first time it is called, will
179             # instantiate a Net::Peep::BC object as necessary
180              
181 0         0 $self->peck();
182              
183 0         0 while (1) {
184              
185 0         0 $nfound = File::Tail::select(undef,undef,undef,60,@logFileTails);
186             # hmmm ... don't quite understand what interval does ... [collin]
187 0 0       0 unless ($nfound) {
188 0         0 for my $filetail (@logFileTails) {
189 0         0 $filetail->interval;
190             }
191             }
192              
193 0         0 for my $filetail (@logFileTails) {
194 0 0       0 $self->tail($filetail->read) unless $filetail->predict;
195             }
196             }
197              
198 0         0 return 1;
199              
200             } # end sub loop
201              
202             sub peck {
203              
204 0     0 0 0 my $self = shift;
205              
206 0         0 my $configuration = $self->conf();
207              
208 0 0       0 unless (exists $self->{"__PEEP"}) {
209 0 0       0 if ($configuration->getOptions()) {
210 0         0 $self->{"__PEEP"} = Net::Peep::BC->new( $self->name(), $configuration );
211             } else {
212 0         0 confess "Error: Expecting options to have been parsed by now.";
213             }
214             }
215              
216 0         0 return $self->{"__PEEP"};
217              
218             } # end sub peck
219              
220             sub tail {
221              
222 0     0 0 0 my $self = shift;
223 0         0 my $line = shift;
224            
225 0         0 chomp $line;
226            
227 0         0 $self->logger()->debug(9,"Checking [$line] ...");
228            
229 0         0 my $conf = $self->conf();
230            
231 0         0 my $found = 0;
232            
233             # filter the events based on which groups or option letters
234             # are specified
235 0         0 my @events = grep $self->filter($_), $self->events();
236            
237 0         0 for my $event (@events) {
238            
239             # if we've already matched an event ignore the remaining events
240            
241 0 0       0 unless ($found) {
242            
243 0         0 my $name = $event->name();
244 0         0 my $location = $event->location();
245 0         0 my $priority = $event->priority();
246 0         0 my $status = $event->notification();
247 0         0 my $regex = $event->regex();
248            
249 0         0 $self->logger()->debug(9,"\tTrying to match regex [$regex] for event [$name]");
250            
251 0 0       0 if ($line =~ /$regex/) {
252            
253 0         0 $self->logger()->debug(5,"$name: $line");
254            
255 0         0 $self->peck()->send(
256             'logparser',
257             'type' => 0,
258             'sound' => $name,
259             'location' => $location,
260             'priority' => $priority,
261             'volume' => 255
262             );
263            
264 0         0 my $notifier = new Net::Peep::Notifier;
265 0         0 my $notification = new Net::Peep::Notification;
266            
267 0         0 $notification->client($self->name());
268 0         0 $notification->hostname($Net::Peep::Notifier::HOSTNAME);
269 0         0 $notification->status($status);
270 0         0 $notification->datetime(time());
271 0         0 $notification->message("${Net::Peep::Notifier::HOSTNAME}: $name: $line");
272            
273 0         0 $notifier->notify($notification);
274            
275 0         0 $found++;
276            
277             }
278             }
279             }
280            
281 0         0 return 1;
282              
283             } # end sub tail
284              
285             sub parse {
286              
287 1     1 0 19 my $self = shift;
288 1   33     5 my $client = $self->name() || confess "Cannot parse logparser events: Client name attribute not set.";
289 1         6 my @text = @_;
290              
291 1   33     6 my $conf = $self->conf() || confess "Cannot parse logparser events: Configuration object not found.";
292              
293 1         6 $self->logger()->debug(1,"\t\tParsing events for client [$client] ...");
294              
295 1         12 $self->tempParseDefaults(@text);
296              
297 1         4 my @events = $self->getConfigSection('events',@text);
298              
299 1 50       5 my @version = $self->conf()->versionExists()
300             ? split /\./, $self->conf()->getVersion()
301             : ();
302              
303 1 50 33     26 if (@version && $version[0] >= 0 && $version[1] >= 4 && $version[2] > 3) {
    0 33        
      33        
      0        
      0        
      0        
304              
305 1         5 while (my $line = shift @events) {
306 10 50 33     81 next if $line =~ /^\s*#/ || $line =~ /^\s*$/;
307              
308 10         12 my $name;
309 10 50       72 if ($line =~ /^\s*([\w-]+)\s+([\w-]+)\s+(\d+)\s+(\d+)\s+(\w+)\s+"(.*)"\s+([\w\-\.]+)/) {
310              
311 10         76 my $event = new Net::Peep::Client::Logparser::Event;
312 10         31 $event->name($1);
313 10         34 $event->group($2);
314 10         29 $event->location($3);
315 10         26 $event->priority($4);
316 10         22 $event->notification($5);
317 10         28 $event->regex($6);
318 10         27 $event->hosts($7);
319            
320 10         23 $self->addEvent($event);
321 10         30 $self->logger()->debug(1,"\t\t\tClient event [$1] added.");
322              
323             }
324              
325             }
326              
327             } elsif (@version && $version[0] >= 0 && $version[1] >= 4 && $version[2] > 1) {
328              
329 0         0 while (my $line = shift @events) {
330 0 0 0     0 next if $line =~ /^\s*#/ || $line =~ /^\s*$/;
331              
332 0         0 my $name;
333 0 0       0 if ($line =~ /^\s*([\w-]+)\s+([\w-]+)\s+([a-zA-Z])\s+(\d+)\s+(\d+)\s+"(.*)"/) {
334              
335 0         0 my $event = new Net::Peep::Client::Logparser::Event;
336 0         0 $event->name($1);
337 0         0 $event->group($2);
338 0         0 $event->letter($3);
339 0         0 $event->location($4);
340 0         0 $event->priority($5);
341 0         0 $event->regex($7);
342            
343 0         0 $self->addEvent($event);
344 0         0 $self->logger()->debug(1,"\t\t\tClient event [$1] added.");
345              
346             }
347              
348             }
349              
350             } else {
351              
352 0         0 while (my $line = shift @events) {
353 0 0 0     0 next if $line =~ /^\s*#/ || $line =~ /^\s*$/;
354            
355 0         0 my $name;
356 0 0       0 if ($line =~ /([\w-]+)\s+([a-zA-Z])\s+(\d+)\s+(\d+)\s+"(.*)"/) {
357              
358 0         0 my $event = new Net::Peep::Client::Logparser::Event;
359 0         0 $event->name($1);
360 0         0 $event->letter($3);
361 0         0 $event->location($4);
362 0         0 $event->priority($5);
363 0         0 $event->regex($7);
364            
365 0         0 $self->addEvent($event);
366 0         0 $self->logger()->debug(1,"\t\t\tClient event [$1] added.");
367              
368             }
369              
370             }
371              
372             }
373              
374 1         12 return @text;
375              
376             } # end sub parse
377              
378             sub addEvent {
379              
380 10     10 0 11 my $self = shift;
381 10   33     25 my $event = shift || confess "Cannot add logparser event: No event was provided.";
382 10         7 push @{$self->{'EVENTS'}}, $event;
  10         24  
383 10         16 return 1;
384              
385             } # end sub addEvent
386              
387             sub events {
388              
389             # return an array of events identified by calls to the event
390             # method
391              
392 0     0 0   my $self = shift;
393 0 0         return wantarray ? @{$self->{'EVENTS'}} : $self->{'EVENTS'};
  0            
394              
395             } # events
396              
397             1;
398              
399             __END__