#!/usr/bin/perl -w # # $Id: log_watcher.pl,v 1.1.1.1 2004/07/27 10:21:05 mpeppler Exp $ use strict; # Steve Wechsler, smw@who.net # written 6/98 # Last modified 1/99 # Updated by Michael Peppler (mpeppler@peppler.org) # handle dealock information printed to the log via the "print deadlock" # config option. # strict/warnings. # Last modified 7/23/2002 # This script is designed to watch a continuously updating error log, # parse it for certain keywords, and take actions based on those # keywords. It was intended for use with Sybase Adaptive Server, # but should work with any type of log file that is continuously # appended to, assuming you have written an appropriate control file. # If you install this script, I'd appreciate an email letting me know. # likewise if you make improvements. # This script was inspired by the Swatch package by Todd Atkins (modified # by Todd Boss for Sybase). If you've seen swatch you will notice a lot # of similarities. If you're interested in swatch, look for it here: # http://www.bossconsulting.com/sybase_dba/sublevels/swatch.html # I liked the way that package worked but was frustrated by the difficulty # encountered when trying to debug, since swatch created a new perl script # every time you ran it. This script attempts to accomplish the same # thing using just a config file. # Config file format: # offset: # search_stringaction[action arguments[number of additional lines to read]] # allowable actions are: # mail beep ignore pipe exit # However, pipe is not implemented. # To install, you will need to make the following changes: # 1: Make sure the first line of the script points to your installation of # perl # 2: Change $MAIL_RECIPIENTS to point to whatever's appropriate for your site # 3: If you have a paging system, make appropriate changes to sub beep # (if you don't, don't use the beep action) # To run, I typically nohup it and redirect to /dev/null. A good place # to put it might be in your RUNserver file, as long as you set the # exit action properly on "ueshutdown" (you'll have to kill it manually if # you end up doing a "kill -9" on your server). # The config file as written will work for Sybase 11.0.2. # For other versions you will, at the least, need to change the offset. # To do list: # o Implement pipe subroutine # o Convert to NT Perl # One last note: # When I wrote this script, I was not particularly well versed in Perl. # I've learned a bit since then, and, looking through the script, there # are a lot of things I would have done differently had I known better # at the time. However, given the limited time I've had to work on this # lately, I figured it would be better to adopt the "if it ain't broke, # don't fix it" attitude. It works, and doesn't use a lot of CPU time, # so I really haven't had much to complain about. use vars qw($MAIL_RECIPIENTS $PAGING_GROUP $BIN_DIR); use vars qw($CONFIG $INPUT $LOGFILE $SAVE_FILE $ERROR_TIMEOUT $CLEANUP_INTERVAL); $MAIL_RECIPIENTS = 'mpeppler@kagi.com ty@kagi.com'; $PAGING_GROUP = "dba"; $BIN_DIR = "/sqldb/bin"; use Config; use Getopt::Std; use File::Basename; select STDERR; $| = 1; select STDOUT; $| = 1; #$\ = "\n"; if (@ARGV < 2 || @ARGV > 3) { &usage; exit 1; } sub usage { my $b = basename $0; print "Usage:\n"; print "$b config_file input_file [log file]\n"; exit 1; } ($CONFIG, $INPUT, $LOGFILE) = @ARGV; $LOGFILE = "&STDOUT" unless ($LOGFILE); $SAVE_FILE = "save.$CONFIG"; $ERROR_TIMEOUT = 5 * 60; # number of seconds before repeating an error # if you want to get all duplicate error messages, set $ERROR_TIMEOUT to 0 $CLEANUP_INTERVAL = 60 * 60; # delete old entries in hash every hour open CONFIG or die "Unable to open file $CONFIG - $!\n"; my $offset = 0; my $i = 0; my $line_no = 0; my @string; my @action; my @actionargs; my @numrows; while () { $line_no++; if (/^offset:/i) { my $junk; ($junk, $offset) = split /\s+/; # print "offset is $offset\n"; next; } if ($_ !~ /^\#/ && $_ !~ /^\s+$/ && $_ !~ /^$/ ) { ($string[$i], $action[$i], $actionargs[$i], $numrows[$i]) = split /\t+/, $_; $numrows[$i] =~ s/\s//g; if ($string[$i] eq "" || $action[$i] eq "") { print @string;print @action; die "Invalid entry in $CONFIG at line $line_no\n"; } $i++; } } close CONFIG; my $last_cleanup_time = time; open LOGFILE, ">>$LOGFILE" or die "Unable to open $LOGFILE for writing: $!\n"; select LOGFILE; $| = 1; open INPUT or die "Unable to open input file $INPUT\n"; seek INPUT, 0, 2; my $curpos = tell INPUT; my $got_input = 0; my %errtime; my @block; my $key; my $got_error; while (1) { while () { if ($got_input == 1) { # print "Next line was $_"; $got_input = 0; } $curpos = tell INPUT; # print "in INPUT loop\n"; # Delete old error messages so the hash table doesn't get too big... if ($last_cleanup_time + $CLEANUP_INTERVAL < time) { foreach $key (keys %errtime) { if ($errtime{$key} + $ERROR_TIMEOUT < time) { # print "deleting time for $key\n"; delete $errtime{$key}; } } $last_cleanup_time = time; } # Parse the error log... for (my $str_index = 0; $str_index < @string; $str_index++) { if ($_ =~ /$string[$str_index]/i) { @block = (); push(@block, $_); $key = substr $_, $offset; if ($numrows[$str_index] && $numrows[$str_index] =~ /\S/) { my $n = 0; my $j = 1; my $pat = ''; if($numrows[$str_index] =~ /^\d+$/ && $numrows[$str_index] > 0) { $n = $numrows[$str_index]; } else { chomp($pat = $numrows[$str_index]); } select undef, undef, undef, 0.5; # wait a half second if($n) { while( $j <= $n && ($_ = )) { $block[$j++] = $_; # print "Retrieved line $j:\n$_"; $key .= substr $_, $offset; select undef, undef, undef, 0.5; # wait a half second } } else { while() { push(@block, $_); print "Retrieved line:\n$_ Pattern: $pat\n"; # last if $_ =~ /$pat/i; last if /$pat/i; # $key .= substr $_, $offset; select undef, undef, undef, 0.5; # wait a half second } } $curpos = tell INPUT; } $_ = $action[$str_index]; my $t=time; $got_error = 1; SWITCH: { # The following logic prevents the situation where an error message # may occur multiple times, triggering the same action. # Setting the variable $ERROR_TIMEOUT will prevent duplicate error messages # from triggering an action more often than $ERROR_TIMEOUT seconds. # Of course, the error messages must be indentical, and $offset must be # set properly for it to work. (/mail/ || /beep/ || /pipe/) && do { if (! defined($errtime{$key}) || ($errtime{$key} + $ERROR_TIMEOUT) <= time) { $errtime{$key} = time; } else { if ($errtime{$key} + $ERROR_TIMEOUT > time) { print LOGFILE "Current time = $t; last error time was $errtime{$key}; skipping\n"; print LOGFILE $_; last SWITCH; } } }; /mail/ && do { mail($actionargs[$str_index], @block); last SWITCH; }; /beep/ && do { beep($actionargs[$str_index], @block); last SWITCH; }; /pipe/ && do { pipe($actionargs[$str_index], @block); last SWITCH; }; /exit/ && do { close LOGFILE; close INPUT; exit 0; }; /ignore/ && do { last SWITCH; }; // && do { last SWITCH; }; } } } } # print "sl sleep 5; seek INPUT, $curpos, 0; } sub mail { chomp(my $args = shift); my @lines = @_; my ($subject, $recipients) = split /\|/, $args; if (!defined($recipients) || $recipients eq "") { $recipients = $MAIL_RECIPIENTS; } chomp($subject = "*** $INPUT: $subject"); # $command = "echo '@lines' | /usr/bin/Mail -s \'$subject' $recipients"; # print LOGFILE "$command \n"; # $output = `$command 2>&1`; # if ($? != 0) { # print LOGFILE "Error occurred while sending mail:\n$output"; # } if(open(MAIL, "|/usr/sbin/sendmail -t")) { print MAIL "To: $recipients\n"; print MAIL "Subject: $subject\n"; print MAIL "\n"; print MAIL @lines; close(MAIL); } else { print LOGFILE "Can't run sendmail: $!\n"; } } sub beep { # print "beeping...\n"; my $args = shift; chomp $args; # print "Args are $args\n"; my @lines = @_; my ($beep, $group, $recipients) = split /\|/, $args; if ($group eq "") { $group = "default"; } # print length $recipients; if ($recipients eq "") { $recipients = $PAGING_GROUP; } my ($basename, $dir, $suffix) = fileparse($INPUT); my $pg_key = $basename; # Create a key by taking every other character from the first line of the # error message # print "creating key...\n"; $key =~ s/\n/ /g; for ($i = 0; $i < length $key && length $pg_key < 32; $i += 2) { $pg_key .= substr($key, $i, 1); } my $msg = substr "$INPUT: $key", 0, 128; $key =~ s/ //g; my $command = "$BIN_DIR/pg_page.pl -G '$group' -g '$recipients' '$pg_key' '$msg'"; print LOGFILE "$command\n"; my $output = `$command 2>&1`; if ($? != 0) { print "Error occurred while sending beep:\n$output"; } }