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


  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()};


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.


This is an object-oriented module. Available 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 argument, and returns the new object.


  $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.)


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 insufficient space to do so, we may have trouble. It also means this method may take a while to return for large compressed logs.




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 recommended 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.)


  while $hash_ref ( $object->next() ) {


  while $hash_ref ( <$object> ) {


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.)


Goes forward a specified number of (logical) lines, or 1 if unspecified. It will throw an error if it fails to seek as requested.

Returns true on success.




Goes backward a specified number of (logical) 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.




Goes to the beginning of the file, no matter how far away that is.

Returns true on success.


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.


Returns the current logical line number.

Note that line numbers start at zero, where 0 is the absolute beginning of the file.


  $line_num = $object->get_line_number();


Goes to a specific logical line number. (Preferably one that exits...)


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 responsibility is to identify 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.  (We trust the logs. ;) )
                $line_info{program} = $line ~= m/$regrex/;

        # Continue parsing

        return \%line_info;


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.



Depreciated: No longer needed. An empty stub exists for backwards-compatibility.


Returns the next line of data, as a string, from the logfile. This is raw data from the logfile, separated by the current input separator.


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.


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.


Scalar::Util, File::Basename, IO::File, Mail::Log::Exceptions


IO::Uncompress::AnyUncompress, File::Temp


Daniel T. Staal


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.)


November 14, 2015 (1.4.2) - Switched from depreciated 'Module::Build::ModuleInfo to it's replacement 'Module::Metadata' and included it in configure_requires. (As it is no longer part of the Module::Build package.)

February 8, 2014 (1.4.1) - Switched to using Perl-standard environment variables for checking to run author tests. (Should now test cleanly on Windows.)

April 17, 2009 (1.4.0) - Simplified subclassing: No longer need to call _set_current_position_as_next_line in subclass. (A stub exists for backwards compatibility.)

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 - Initial version.


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.