SyslogScan::SyslogEntry - parses generic lines in syslog files.


SyslogScan documentation Contained in the SyslogScan distribution.

Index


Code Index:

NAME

Top

SyslogScan::SyslogEntry -- parses generic lines in syslog files.

SYNOPSIS

Top

    use SyslogScan::SyslogEntry;

    open(FH,"/var/log/syslog");

    my $entry;

    # reads from filehandle $fh and returns an object
    # of a subclass of SyslogEntry.
    while ($entry = new SyslogScan::SyslogEntry (\*FH))
    {
	# process $entry
    }

DESCRIPTION

Top

All Syslog object share these data structures: month, day, time, machine, executable, tag (optional), content.

For example, if a syslog line looks like:

Jun 13 02:32:27 satellife in.identd[25994]: connect from mail.missouri.edu

then the line returned by 'new SyslogEntry' will return a SyslogEntry-derived object with at least this set of parameters:

 month => Jun,
 day => 13,
 time => 02:32:27,
 machine => satellife,
 executable => in.identd,
 tag => 25994,
 content => connect from mail.missouri.edu,
 unix_time => 834633147,
 raw => Jun 13 02:32:27 satellife in.identd[25994]: connect from mail.missouri.edu

Since the executable is 'in.identd', SyslogEntry.pm will look for a class called "SyslogScan::In_identdLine" derived from SyslogEntry, and attempt to call that class's parseContent method. If no such In_identdLine class is in use, then the returned object is of the default "SyslogScan::UnsupportedEntry" class.

If the In_identdLine class throws a die() exception, SyslogEntry.pm will catch the die() and return a "SyslogScan::BotchedEntry" object containing the exception in "$errorString" and the failed handler in "brokenHandler".

"new SyslogEntry" returns the undefined value if at EOF.

BUGS

Top

In retrospect, this model of passing control to subclasses based on the name of the controlling program doesn't work exceptionally elegantly in perl. I would probably do it more conventionally if I had it to do over again.

AUTHOR and COPYRIGHT

Top

SEE ALSO

Top

SyslogScan::SendmailLine


SyslogScan documentation Contained in the SyslogScan distribution.

# SyslogEntry: generic line in a syslog program.

package SyslogScan;

$VERSION = 0.31;
sub Version { $VERSION };


package SyslogScan::SyslogEntry;

use SyslogScan::ParseDate;

$VERSION = 0.31;
sub Version { $VERSION };

use SyslogScan::UnsupportedEntry;
use Carp;
use strict;

# to handle 'last message repeated n times' lines
my %gLastLineByHost;
my $gLineToRepeat;
my $gFinalMonth;
my $gFinalDay;
my $gFinalTime;
my $gRepeatCount = 0;

my %gTable = 
    (
# examples:
#     'cli'         =>   'SyslogScan::AnnexEntry',
#     'slip'        =>   'SyslogScan::AnnexEntry',
#     'telnet_cmd'  =>   'SyslogScan::AnnexEntry',
#     'ppp'         =>   'SyslogScan::AnnexEntry',
#     'rlogin_rdr'  =>   'SyslogScan::AnnexEntry',
     );

my $pIsSubclass = sub {
    my($superclass,$possibleSubclass) = @_;
    my(@superclassList);
    
    die "illegal subclass (has whitespace)" if
	$possibleSubclass =~ /\s/;
    @superclassList = eval '@' . "$possibleSubclass" . "::ISA";
    return 't' if (grep (($superclass eq $_), @superclassList));
    '';
};

sub new
{
    my $staticType = shift;
    my $SYSLOG = shift;

    defined $SYSLOG or croak("syslog not defined");

    my ($self, $className, $line);

    # check if we are repeating ourselves
    if ($gRepeatCount)
    {
	$line = $gLineToRepeat;
    }
    else
    {
	# read the next syslog line
	no strict 'refs';
	defined($line = <$SYSLOG>) || return undef;  # at EOF
	use strict 'refs';
	if (chop($line) ne "\n")
	{
	    warn "Discarding final line which was not newline-terminated.\n";
	    print STDERR "  (consider using 'tail -f syslog')\n";
	    return undef;
	}
    }
    
    # parse a line like: 'Jun 13 02:32:27 satellife mydaemon[25994]: foo'
    my ($month,$day,$time,$machine,$rest) =
	split ' ', $line, 5;

    # check for 'last line repeated n times' message
    if ($rest =~ /^last message repeated (\d+) time/)
    {
	$gRepeatCount and
	    die "repetition of 'last message repeated' line!?";
	$gRepeatCount = $1;
	$gLineToRepeat = $gLastLineByHost{$machine};
	($gFinalMonth, $gFinalDay, $gFinalTime) = ($month, $day, $time);
	$gRepeatCount ||
	    die "repetition of length 0!?";
	return SyslogScan::SyslogEntry -> new($SYSLOG);
    }

    if ($gRepeatCount)
    {
	if ($gRepeatCount == 1)  # on last repetition
	{
	    ($month, $day, $time) = ($gFinalMonth, $gFinalDay, $gFinalTime);
	}
	else
	{
	    ($month, $day, $time) = ();  # cannot precisely know time
	}
	$gRepeatCount--;
    }

    $gLastLineByHost{$machine} = $line;
	
    my ($executable,$tag,$content) =
	$rest =~ /^([^\:\[\]]+)(\[\d+\])?\: (.*)/;
    $tag =~ s/\[(.+)\]/$1/ if defined $tag;

    if (! defined $executable)
    {
	$rest and
	    print STDERR "executable not defined in line: $line\n"
		unless $::gbQuiet;
    }

    # fill in my 'self' array
    $self = {
	"content" => $content,
	"month" => $month,
	"day" => $day,
	"time" => $time,
	"machine" => $machine,
	"executable" => $executable,
	"tag" => $tag,
	"raw" => $line
	};

    if (defined $time)
    {
	my $date = "$month $day $time";
	$self->{"unix_time"} = SyslogScan::ParseDate::parseDate($date);
    }

    # check for possible i/o error
    if ($line =~ m^I/O error^ and $` !~ /\bstat=/)
    {
	print STDERR "may be syslog I/O error in line:\n  $line\n"
	    unless $::gbQuiet;
	$$self{suspectIOError} = 1;
    }

    # Make first letter of program capital, and change . to _,
    # so the module to handle 'in.identd' is named "In_identdLine.pm"

    my $oldChar = substr($executable,0,1);
    substr($executable,0,1) =~ tr/a-z/A-Z/;
    my $handlerClass = "SyslogScan::" . $executable . "Line";
    $handlerClass =~ s/[\. ]/_/g;
    substr($executable,0,1) = $oldChar;

    # If the module to handle this program has been "use"'d,
    # then subclass our object and call its parseContent() method.
    if (&$pIsSubclass("SyslogScan::SyslogEntry",$handlerClass))
    {
	bless($self,$handlerClass);
    }
    elsif (defined ($gTable{$executable}))
    {
	bless($self,$gTable{$executable});
    }
    else
    {
	# this line is not supported by a handler class
	bless($self,"SyslogScan::UnsupportedEntry");
    }

    # TODO: get rid of 'type' in favor of checking ref
    eval
    {
	$self -> parseContent;
    };

    if ($@ ne "")
    {
	# provide "escape hatches" so a module can halt the
	# entire program execution if it really needs to
	if (($@ =~ /SYSLOGMODULEFATAL/) ||
	    defined $$self{"ERRORS ARE FATAL"})
	{
	    die "fatal module error: $@" ;
	}
	
	# catch non-fatal errors so flawed module does not break others
	my ($brokenHandler) = ref $self;
	bless ($self, "SyslogScan::BotchedEntry");
	$$self{"brokenHandler"} = $brokenHandler;
	$$self{"errorString"} = $@;
	print STDERR "SyslogEntry.pm caught $brokenHandler module error: \n" .
	    "  $@\n" .
	    "  returning BotchedEntry object\n";
    }

    $self;
}

sub parseContent
{
    my ($self) = @_;
    die "class ", ref($self), " did not override parseContent!\n";
}

# access methods

sub content    { return ( (my $self = shift)->{"content"});}
sub raw        { return ( (my $self = shift)->{"raw"});}
sub month      { return ( (my $self = shift)->{"month"});}
sub day        { return ( (my $self = shift)->{"day"});}
sub time       { return ( (my $self = shift)->{"time"});}
sub machine    { return ( (my $self = shift)->{"machine"});}
sub executable { return ( (my $self = shift)->{"executable"});}
sub tag        { return ( (my $self = shift)->{"tag"});}
sub unix_time  { return ( (my $self = shift)->{"unix_time"});}

1;

__END__