NAME
    Mail::Log::Parse - Parse and return info in maillogs

SYNOPSIS
      use Mail::Log::Parse;

      $object = Mail::Log::Parse->new({  log_file => '/path/to/logfile'  });
      %line_info = %{object->next()};
      
  $line_num = $object->get_line_number();
      
  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 seeking 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 initialized. (Whether it has a file.)
    Numeric context throws an error.

    Oh, and iterator 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 in 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...)

SUBCLASSING
    This class is useless without subclasses to handle specific file
    formats. As such, attempts have been made to make subclassing as
    painless as possible. In general, you should only ever have to implement
    one method: "_parse_next_line".

    "_parse_next_line" will be called whenever another line of the log needs
    to be read. Its responsiblity is to indentify the next line, report
    where that is in the actual file, and to parse that line.

    Specifically, it should *not* assume that every line in the input file
    is a valid log line. It is expected to check first.

    Mail::Log::Parse is (as of v1.3) a cached inside-out object. If you
    don't know what that means, ignore it: just writing "_parse_next_line"
    correctly is enough. However, if you find you need to store sub-class
    object info for some reason, and want to use an inside-out object syntax
    yourself, "$$self == refaddr $self". Which is useful and fast.

    Speed *is* important. It is not unlikely for someone to try to parse
    through a week's worth of logs from a dozen boxes, where each day's log
    is hundreds of megabytes worth of data. Be as good as you can.

    One other thing: Realize that you may also be subclassed. Even if you
    parse every possible option of some log format, someone somewhere will
    probably have a customized version with a slightly different format. If
    you've done your job well, they'll be able to use your parser and just
    extend it slightly. Key to this is to leave the *unaltered* line in the
    return hash under the 'text' key.

  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, using the Mail::Log::Parse utilty method.
                    $line = $self->_get_data_line() or return undef;

                    # Program name.
                    $line_info{program} = $line ~= m/$regrex/;
            }

            # Ok, let's update where we are, using the Mail::Log::Parse utility method.
            $self->_set_current_position_as_next_line();

            # Continue parsing
            ...
            
        return \%line_info;
     }

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.

  _clear_buffer
    Clears the internal buffer of any data that may have been read into it
    so far. Normally you should never need to use this: It is provided only
    for those rare cases where something that has already been read may be
    changed because of outside input. (For instance: You can change the year
    dates are assumed to be in during mid-read on Postfix.)

    Avoid using unless actually needed.

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
    April 9, 2009 (1.3.1) - Documentation fixes, better handling of trying
    to work without a valid logfile.

    Dec 23, 2008 (1.3.0) - Further code speedups. Now stores a cache of the
    refaddr for easy and quick access.

    Dec 09, 2008 (1.2.10) - Profiled and sped up code. (Cut processing time
    in half for some cases.)

    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.

