SYNOPSIS
use Mail::Log::Parse;
$object = Mail::Log::Parse->new({ log_file => '/path/to/logfile' });
%line_info = %{object->next()};
$line_num = $object->get_line_num();
if ( $object->go_forward($amount) ) {
...
}
if ( $object->go_backward($amount) ) {
...
}
%line_info = %{object->previous()};
DESCRIPTION
This is the root-level module for a generic mail log file parser. It is
capable of opening either a compressed or uncompressed logfile, and
either stepping through it line by line, or seaking around in it based
on the logical lines. (Lines not pertaining to the type of log currently
being searched are skipped, as if they don't exist.)
On it's own it doesn't actually do much: You'll need a subclass that can
parse a particular program's log entries. But such subclasses are
designed to be easy to write and use.
USAGE
This is an object-oriented module. Avalible object methods are below.
In a string context, it will return a string specifying the path to the
file and the current line number. In a boolean context, it will return
whether it has been correctly initilized. (Whether it has a file.)
Numeric context throws an error.
Oh, and interator context ('<>') returns the same as 'next'...
new (constructor)
The base constructor for the Mail::Log::Parse classes. It takes an
(optional) hash containing path to the logfile as an arugment, and
returns the new object.
Example:
$object = Mail::Log::Parse->new({ log_file => '/path/to/logfile' });
Note that it is an error to call any method other than `set_logfile' if
you have not passed it in the constructor.
Optional keys in the hash are 'buffer_length' and 'debug'. The buffer
length is the number of lines to read at a time (and store in the
internal buffer). Default is 128. Setting debug to a true value will
result is some debugging information being printed to STDERR. (I reserve
the right to remove or change the debug info at any time.)
set_logfile
Sets the logfile that this object will attempt to parse. It will throw
exceptions if it can't open the file for any reason, and will return
true on success.
Files can be compressed or uncompressed: If they are compressed, then
`IO::Uncompress::AnyUncompress' must be installed with the relevant
decompression libraries. (As well as version 0.17 or better of
File::Temp.) Currently only 'tgz', 'zip', 'gz', and 'bz2' archives are
supported, but there is no technical reason not to support more. (It
just keeps a couple of lines of code shorter.)
Note that to support seeking in the file the log will be uncompressed to
disk before it is read: If there is insufficent space to do so, we may
have trouble. It also means this method may take a while to return for
large compressed logs.
Example:
$object->set_logfile('path/to/file');
next
Returns a reference to a hash of the next parsable line of the log, or
'undef' on end of file/failure.
There are a couple of required keys that any parser must implement:
timestamp, program, id, text.
Where `timestamp' must the the unix timestamp, `program' must be the
name of the program that reported the logline (Sub-programs are
recommened to be listed, if possible), `id' is the tracking ID for that
message, as reported by the program, and `text' is the text following
any 'standard' headers. (Usually, minus those already required keys.)
This version is just a placeholder: It will return a
'Mail::Log::Exceptions::Unimplemented' exception if called. Subclasses
are expected to override the `_parse_next_line' method to get an
operable parser. (And that is the only method needed to be overridden
for a working subclass.)
Other 'standard' fields that are expected in a certain format (but are
not required to always be present) are 'from', 'to', 'size', 'subject',
delay. 'to' should point to an array of addresses. (As listed in the
log. That includes angle brackets, usually.)
Example:
while $hash_ref ( $object->next() ) {
...
}
or...
while $hash_ref ( <$object> ) {
...
}
previous
Returns a reference to a hash of the previous line of the log, or undef
on failure/beginning of file.
See `next' for details: It works nearly exactly the same. (In fact, it
calls next as a parser.)
go_forward
Goes forward a specifed number of (logical) lines, or 1 if unspecified.
It will throw an error if it fails to seek as requested.
Returns true on success.
Example:
$object->go_forward(4);
go_backward
Goes backward a specifed number of (logcial) lines, or 1 if unspecified.
It will throw an error if it fails to seek as requested.
If the seek would go beyond the beginning of the file, it will go to the
beginning of the file.
Returns true on success.
Example:
$object->go_backward(4);
go_to_beginning
Goes to the beginning of the file, no matter how far away that is.
Returns true on success.
go_to_end
Goes to the end of the file, no matter where it is.
This attempts to be efficient about it, skipping where it can.
Returns true on success.
get_line_number
Returns the current logical line number.
Note that line numbers start at zero, where 0 is the absolute beginning
of the file.
Example:
$line_num = $object->get_line_number();
go_to_line_number
Goes to a specific logical line number. (Preferably one that exits...)
UTLITY METHODS
The following methods are not for general consumption: They are
specifically provided for use in implementing subclasses. Using them
incorrectly, or outside a subclass, can get the object into an invalid
state.
ONLY USE IF YOU ARE IMPLEMENTING A SUBCLASS.
_set_current_position_as_next_line
Sets the current position in the file as the next 'line' position in
sequence.
Call once you have determined that the current line of data (as returned
from `_get_data_line') is parsable in the currently understood format.
_get_data_line
Returns the next line of data, as a string, from the logfile. This is
raw data from the logfile, seperated by the current input seperator.
Suggested usage:
Suggestion on how to use the above two methods to implement a
'_parse_next_line' routine in a subclass:
sub _parse_next_line {
my ($self) = @_;
# The hash we will return.
my %line_info = ( program => '' );
# Some temp variables.
my $line;
# In a mixed-log enviornment, we can't count on any
# particular line being something we can parse. Keep
# going until we can.
while ( $line_info{program} !~ m/$program_name/ ) {
# Read the line.
$line = $self->_get_data_line() or return undef;
# Program name.
$line_info{program} = $line ~= m/$regrex/;
}
# Ok, let's update our info.
$self->_set_current_position_as_next_line();
# Continue parsing
...
BUGS
`go_forward' and `go_backward' at the moment don't test for negative
numbers. They may or may not work with a negative number of lines: It
depends where you are in the file and what you've read so far.
Those two methods should do slightly better on 'success' testing, to
return better values. (They basically always return true at the moment.)
`get_line_number' will return one less than the true line number if you
are at the end of the file, and the buffer was completely filled. (So
that the end of the file is the last space of the buffer.) Changing the
buffer size or just going back and re-reading so that the buffer is
restarted at a different location will allow you to retrieve the correct
file length.
REQUIRES
Scalar::Util, File::Basename, IO::File, Mail::Log::Exceptions
RECOMMENDS
IO::Uncompress::AnyUncompress, File::Temp
AUTHOR
Daniel T. Staal
DStaal@usa.net
SEE ALSO
Parse::Syslog::Mail, which does some of what this module does. (This
module is a result of running into what that module doesn't support.
Namely seeking through a file, both forwards and back.)
HISTORY
Nov 28, 2008 - Documentation fixes.
Nov 18, 2008 - Now buffers reading, and prefers data from the buffer.
Oct 24, 2008 - File::Temp now optional; only required for uncompressed
files. Added go_to_line_number for slightly better functionality.
Oct 14, 2008 - Found that I need File::Temp of at least version 0.17.
Oct 13, 2008 - Fixed tests so they do a better job of checking if they
need to skip.
Oct 6, 2008 - Inital version.
COPYRIGHT and LICENSE
Copyright (c) 2008 Daniel T. Staal. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
This copyright will expire in 30 years, or 5 years after the author's
death, whichever is longer.