File Coverage

blib/lib/SNMP/Server/Logtail.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             # -*- mode: perl; coding: iso-8859-1 -*-
3             # Author: Peter Corlett
4             # Contact: abuse@cabal.org.uk
5             # Revision: $Revision$
6             # Date: $Date$
7             # Copyright: (c) 2005 Peter Corlett - All Rights Reserved
8              
9             package SNMP::Server::Logtail;
10             require v5.8.1; # need 5.8.1 for reliable threads
11 1     1   6404 use strict;
  1         3  
  1         46  
12 1     1   6 use vars qw( $VERSION @ISA @EXPORT );
  1         2  
  1         139  
13             # $Id: Logtail.pm 35 2005-09-27 13:27:28Z abuse $
14             $VERSION = do { my @r=(q$Revision: 35 $=~/\d+/g); sprintf "%d."."%03d"x$#r,@r };
15             @EXPORT=qw( snmpd_init add_oidmap add_logfile snmpd_run );
16             @ISA=qw( Exporter );
17              
18 1     1   3075 use threads;
  0            
  0            
19             use threads::shared;
20              
21             use CGI::Carp qw(set_progname);
22             use Data::Dumper;
23             require Exporter;
24              
25             # This hash contains the counters for various events
26             my %counter;
27             share %counter;
28              
29             my $stop=0;
30             share $stop;
31              
32             my %oidmap;
33             my %oidnext;
34              
35             my $running;
36             my @threads;
37              
38             my $prefix;
39              
40             =head1 NAME
41              
42             SNMP::Server::Logtail - Tails logfiles and presents counters via SNMP
43              
44             =head1 SYNOPSIS
45              
46             #!/usr/bin/perl
47             use SNMP::Server::Logtail;
48              
49             sub exim_mainlog {
50             my($hash, $line)=@_;
51             $hash->{MSG_IN}++ if /^<= /;
52             $hash->{MSG_OUT}++ if /^=> /;
53             # etc
54             }
55              
56             snmpd_init( -logfile => '/tmp/testlog',
57             -prefix => '.1.3.6.1.4.1.2021.255' );
58             add_oidmap( MSG_IN => 1,
59             MSG_OUT => 2 );
60             add_logfile('/var/log/exim/mainlog' => \&exim_mainlog);
61             snmpd_run();
62              
63             =head1 DESCRIPTION
64              
65             This module implements the core functionality that combines with the
66             Net-SNMP SNMP daemon (not to be confused with the Net::SNMP Perl
67             module) which allows you to monitor updates to a logfile. Typically,
68             it's used to present a MIB of counters that increment whenever certain
69             events are logged. It also detects when the logfile has been rotated
70             and will start monitoring the new logfile when this occurs.
71              
72             The example in SYNOPSIS above is a complete program that Net-SNMP can
73             use. Note that this module imports functions into your namespace, has
74             global variables, and is generally hostile to your script doing
75             anything other than purely being a client to it. This is intentional.
76              
77             To tell Net-SNMP about your new MIB and script, insert the following
78             line into I:
79              
80             pass_persist .1.3.6.1.4.1.2021.255 /path/to/script.pl
81              
82             and then reload Net-SNMP. You can then perform a SNMP query:
83              
84             snmpget -v 1 -c public localhost .1.3.6.1.4.1.2021.255.1
85              
86             You can obviously also read this by using Net::SNMP, MRTG, or similar.
87              
88             The function set with add_logfile() is called back whenever a new
89             entry appears in the logfile. It is passed a hash reference and single
90             lines from the logfile. The function may use the hash as it sees fit,
91             although normally it would just increment/set values in the hash as
92             new log data becomes available to it.
93              
94             The arguments given to add_oidmap() define which entries in the hash
95             are read whenever a SNMP query is made. It contains key/value pairs.
96             The key corresponds to keys in the hash which are read - the value is
97             the suffix of the OID that causes this read. (The OID's prefix is
98             given in snmpd_init.)
99              
100             =head1 FUNCTIONS
101              
102             =over 4
103              
104             =item B
105              
106             snmpd_init( -logfile => '/tmp/testlog',
107             -prefix => '.1.3.6.1.4.1.2021.255' );
108              
109             Initialises the global state for the log tailer. This script's own
110             diagnostics goes to the logfile given with B<-logfile> and the base
111             OID for the MIB you're creating is given with B<-prefix>.
112              
113             =cut
114              
115             sub snmpd_init {
116             my(%opts)=@_;
117              
118             my $logfile=$opts{-logfile};
119             croak "-logfile not set" unless defined $logfile;
120             $prefix=$opts{-prefix};
121             croak "-prefix not set" unless defined $prefix;
122             my $logname=$opts{-logname} || $0;
123              
124             open STDERR, '>>', $logfile
125             or croak "Can't create/append $logfile: $!";
126              
127             set_progname("${logname}[$$]");
128             $running++;
129             warn "Started\n";
130             $|=1;
131             }
132              
133             sub END {
134             warn "Crashed!\n"
135             if $running;
136             }
137              
138             =item B
139              
140             add_oidmap( ACCEPT => 1,
141             REJECT => 2 );
142              
143             This adds a mapping of keywords to OIDs.
144              
145             =cut
146              
147             sub add_oidmap {
148             %oidmap=(%oidmap, reverse @_);
149              
150             my @oids=sort {$a<=>$b} keys %oidmap;
151             $oidnext{''}=$oids[0];
152             foreach(1..$#oids) {
153             $oidnext{$oids[$_-1]}=$oids[$_];
154             }
155             }
156              
157             sub run_monitor {
158             my($logname, $subref)=@_;
159             warn "Starting monitoring $logname\n";
160             my $first=1;
161             until($stop) {
162             open LOG, '<', $logname
163             or croak "Can't open $logname: $!";
164             my($curinode, $cursize)=(stat LOG)[(1, 7)];
165             if($first) {
166             # If this is the first run (i.e. the logfile hasn't been rotate
167             # on us) we seek to the end of the file and start tailing from
168             # there. This is so that all the previous entries in the logfile
169             # don't appear all at once and be potentially graphed as
170             # happening in the last moment.
171             seek LOG, 2, 0;
172             $first=0;
173             }
174             my $rotated=0;
175             until($rotated || $stop) {
176             my($newinode, $newsize)=(stat $logname)[(1, 7)];
177             while(my $line=) {
178             lock %counter;
179             &{$subref}(\%counter, $line);
180             #print "\033[H\033[J", Dumper \%counter, scalar localtime;
181             last if $stop;
182             }
183             sleep 1;
184             # has logfile been rotated?
185             if(defined $newinode && (
186             $newinode != $curinode # file has been rotated
187             || $newsize < $cursize # file has been truncated
188             )
189             ) {
190             $rotated=1;
191             warn "Log rotated, will reopen $logname\n";
192             }
193             }
194             }
195             warn "Stopped monitoring $logname\n";
196             }
197              
198              
199             =item B
200              
201             add_logfile('/var/log/exim/mainlog' => \&exim_mainlog);
202              
203             This adds another logfile to be monitored, and registers a callback
204             function which will be called for each new line that appears in the
205             logfile.
206              
207             =cut
208              
209             sub add_logfile {
210             my($logname, $subref)=@_;
211             my $thread=threads->create('run_monitor', $logname, $subref);
212             push @threads, $thread;
213             }
214              
215             =item B
216              
217             snmpd_run();
218              
219             This starts the main loop. Control will return when Net-SNMP is shut
220             down, so you should only clean up and exit after this occurs.
221              
222             =cut
223              
224             sub snmpd_run {
225             while (my $command=) {
226             chomp $command;
227             if ($command=~/^PING/){
228             #warn "Answering snmpd PING\n" if $DEBUG;
229             print "PONG\n";
230             next;
231             }
232             my $oid=<>;
233             chomp $oid;
234             my($suffix)=$oid=~/^$prefix\.?(.*)$/o;
235             $suffix='' unless defined $suffix;
236             if ($command eq "getnext") {
237             #warn "Answering getnext for suffix $suffix\n" if $DEBUG;
238             if(exists $oidnext{$suffix}) {
239             $suffix=$oidnext{$suffix};
240             } else {
241             $suffix='NONE';
242             }
243             }
244             #warn "Answering get for suffix $suffix\n" if $DEBUG;
245             if(exists $oidmap{$suffix}) {
246             lock %counter;
247             my $value=$counter{$oidmap{$suffix}} || 0;
248             $value &= 0xffffffff;
249             print "$prefix.$suffix\ncounter\n$value\n";
250             } else {
251             print "NONE\n";
252             }
253             }
254              
255             warn "Shutting down...\n";
256             $stop=1;
257             $_->join foreach @threads;
258             warn "Stopped\n";
259             $running=0;
260             }
261              
262             =back
263              
264             =head1 BUGS
265              
266             This documentation is terrible. Reading the example client may be more
267             helpful.
268              
269             =head1 SEE ALSO
270              
271             Net::SNMP
272              
273             =head1 AUTHOR
274              
275             All code and documentation by Peter Corlett .
276              
277             =head1 COPYRIGHT
278              
279             Copyright (C) 2004 Peter Corlett . All rights
280             reserved.
281              
282             This program is free software; you can redistribute it and/or modify
283             it under the same terms as Perl itself.
284              
285             =head1 SUPPORT / WARRANTY
286              
287             This is free software. IT COMES WITHOUT WARRANTY OF ANY KIND.
288              
289             =cut
290            
291             1;
292