#!/usr/bin/perl =head1 NAME syslogtail.pl - Displays running system logs to a terminal with pretty colors =head1 SYNOPSIS syslogtail.pl [-c configfile] =head1 DESCRIPTION Using "tail -F", follows specified log files and acts upon them using handler functions, displaying what you want in a pretty colorized format for easy visual scanning. =head1 OPTIONS The default config file location is ~/.syslogtailrc =over =item -c path/to/configfile =back =head1 CONFIGURATION The config file is straight perl because I couldn't think of an easier human-readable format. %conf = ( '/path/to/log/file' => { 'typ' => 'simple|maillog|named|qmail|qpsmtpd', 'color' => 'green|blue|magenta...' # colors are fed to TERM::ANSIColor } ); "typ"s are matched to functions via the %typehandler variable. Please add your own and send them back so other people can benefit. The "simple" handler simply feeds back the output in the chosen color. "maillog" just has an extra grep in it. "named" is the same. "qmail" pre-processes the line with a tai64nlocal routine to make the date readable. "qpsmtpd" is a doozy. But its not much more than a list of greps and a specialized (but still ugly) color scheme. It also uses tai64nlocal to make the date readable. =head1 REQUIREMENTS =over =item Time::TAI64 version 1.7 (I can't understand 1.8) =item Term::ANSIColor =back =head1 AUTHOR Frank Johnson Eratty at they.orgE. Copyright (c) 2003 Distributable under the Gnu GPL version 2 http://web.they.org/software/ http://web.they.org/software/images/syslogview2.png =head1 VERSION syslogtail.pl v0.7 o Dropped DNS requirement and synlog handler, using syslog-ng now. o Added docs syslogtail.pl v0.6 o Dropped File::Tail in favor of Gnu tail and a touch more parsing syslogtail.pl v0.5 o Semi-official initial release =cut use Term::ANSIColor; ## for djb style logs. use Time::TAI64 1.7 tai64nlocal; use strict; my $conffile = $ENV{'HOME'} . "/.syslogtailrc"; my $width = 0; my ($i, $in, $filelist, $thislog, %conf, %typehandler); ## Change running name so ps/top looks pretty. $0 =~ s/^.*\/([^\/]+)$/$1/; while ($i = shift) { if ($i eq '-c') { die "$0: -c requires a filename arg.\n" if (! ($conffile = shift)); } else { die "$0: invalid arg '$i'\n"; } } %typehandler = ( 'maillog' => \&handle_maillog, 'named' => \&handle_named, 'qpsmtpd' => \&handle_qpsmtp_log, 'qmail' => \&handle_qmail_log, 'simple' => \&handle_simple, ); &readconf($conffile); select(STDOUT); $| = 1; open (IN,"tail -F $filelist |") || die "Can't start file tails."; while ($in = ) { chomp($in); next if ($in =~ /^\s*$/); if ($in =~ m!==> (.+) <==!) { $thislog = $1; next; } &{$typehandler{$conf{$thislog}{typ}}}($in); } sub readconf { my ($conffile) = @_; my ($x); die "$0: conffile '$conffile' not found or unreadable.\n" if (! (-f $conffile && -r $conffile)); eval(`cat $conffile`); foreach $x (keys(%conf)) { die "Can't read $x\n" if (! (-f $x && -r $x)); $filelist .= " $x"; } } sub handle_qpsmtp_log { my ($in) = @_; my @qpTypes; my ($g,$x); return if ($in =~ /550 Hostname rejected/); return if ($in =~ /450 Reverse DNS did n/); $qpTypes[0] = {'color' => 'green', 'grepp' => 'tcpserver: (ok|deny|status)'}; $qpTypes[1] = {'color' => 'bold green on_red', 'grepp' => 'qpsmtpd\[\d+\]: [45]\d\d'}; $qpTypes[2] = {'color' => 'bold green on_red', 'grepp' => 'qpsmtpd\[\d+\]: hnbl plugin: rejected'}; $qpTypes[3] = {'color' => 'bold white on_red', 'grepp' => 'qpsmtpd\[\d+\]: hnbl plugin: DNSfail'}; $qpTypes[4] = {'color' => 'blue on_green', 'grepp' => 'qpsmtpd\[\d+\]: hnbl plugin: passed'}; $qpTypes[5] = {'color' => 'red on_green', 'grepp' => 'qpsmtpd\[\d+\]: 250 Queued!'}; $qpTypes[6] = {'color' => 'bold green', 'grepp' => 'qpsmtpd\[\d+\]:'}; for $x (0, 1, 2, 3, 4, 5, 6) { if ($qpTypes[$x]->{'grepp'}) { $g = $qpTypes[$x]->{'grepp'}; if ($in =~ /$g/) { print colored (&conv_tai64($in),$qpTypes[$x]{'color'}) . "\n"; last; } } else # must be 3, no grepp. let's do a !~ { # actually we shouldn't need to do this at all since we can control that # from the outside. Lets fix that. # XXX if ($in !~ /running plugin/) { print colored (&conv_tai64($in),$qpTypes[$x]{'color'}) . "\n"; } last; } } } sub handle_named { my ($in) = @_; return if ($in =~ /lame server resolving/); &handle_simple($in); } sub handle_maillog { my ($in) = @_; return if ($in !~ /user=/); &handle_simple($in); } sub handle_qmail_log { my ($in) = @_; &handle_simple(&conv_tai64($in)); } sub handle_simple { my ($in) = @_; print colored ($in,$conf{$thislog}{'color'}) . "\n"; } sub conv_tai64 { my ($line) = shift; my (@arr,$str); return $line if ($line !~ /^@/); $line =~ /@(\S+)\s+(\S+.*)$/; @arr = tai64nlocal($1); $line = $2; $str = sprintf "%4d-%02d-%02d %02d:%02d:%02d.%04d",@arr; $str =~ s/\d{6}$/ /; return $str . $line }