File Coverage

blib/lib/Mail/Log/Parse/Postfix.pm
Criterion Covered Total %
statement 87 87 100.0
branch 14 14 100.0
condition 2 3 66.6
subroutine 16 16 100.0
pod 2 2 100.0
total 121 122 99.1


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Mail::Log::Parse::Postfix;
4             {
5             =head1 NAME
6              
7             Mail::Log::Parse::Postfix - Parse and return info in Postfix maillogs
8              
9             =head1 SYNOPSIS
10              
11             use Mail::Log::Parse::Postfix;
12              
13             (See L for more info.)
14              
15             =head1 DESCRIPTION
16              
17             This is a subclass of L, which handles parsing for
18             Postfix mail logs.
19              
20             =head1 USAGE
21              
22             =cut
23              
24 4     4   58170 use strict;
  4         15  
  4         105  
25 4     4   21 use warnings;
  4         9  
  4         115  
26 4     4   19 use Scalar::Util qw(refaddr);
  4         8  
  4         279  
27 4     4   818 use Time::Local;
  4         1749  
  4         278  
28 4     4   1227 use Mail::Log::Parse 1.0400;
  4         84  
  4         159  
29 4     4   22 use Mail::Log::Exceptions;
  4         6  
  4         105  
30 4     4   18 use base qw(Mail::Log::Parse Exporter);
  4         7  
  4         303  
31              
32 4     4   4109 use Memoize;
  4         11047  
  4         255  
33             memoize('timelocal');
34              
35             BEGIN {
36 4     4   23 use Exporter ();
  4         7  
  4         80  
37 4     4   16 use vars qw($VERSION);
  4         9  
  4         146  
38 4     4   1282 $VERSION = '1.0501';
39             }
40              
41             # A constant, to convert month names to month numbers.
42             my %MONTH_NUMBER = ( Jan => 0
43             ,Feb => 1
44             ,Mar => 2
45             ,Apr => 3
46             ,May => 4
47             ,Jun => 5
48             ,Jul => 6
49             ,Aug => 7
50             ,Sep => 8
51             ,Oct => 9
52             ,Nov => 10
53             ,Dec => 11
54             );
55              
56             # We are going to assume we are only run once a day. (Actually, since we only
57             # ever use the _year_...)
58             my @CURR_DATE = localtime;
59              
60             #
61             # Define class variables. Note that they are hashes...
62             #
63              
64             my %log_info;
65              
66             #
67             # DESTROY class variables.
68             #
69             ### IF NOT DONE THERE IS A MEMORY LEAK. ###
70              
71             sub DESTROY {
72 7     7   6963 my ($self) = @_;
73            
74 7         20 delete $log_info{$$self};
75            
76 7         38 $self->SUPER::DESTROY();
77            
78 7         109 return;
79             }
80              
81             sub new {
82 7     7 1 156360 my ($class, $parameters_ref) = @_;
83              
84 7         49 my $self = $class->SUPER::new($parameters_ref);
85              
86 5 100       26 if (defined($parameters_ref->{year})) {
87 1         4 $self->set_year($parameters_ref->{year});
88             }
89              
90 5         16 return $self
91             }
92              
93             =head2 set_year
94              
95             Sets the year, for the log timestamps. If not set, the log is assumed to
96             be for the current year. (Can also be passed in C, with the key 'year'.)
97              
98             =cut
99              
100             sub set_year {
101 5     5 1 4721 my ($self, $year) = @_;
102 5         24 $log_info{refaddr $self}->{year} = $year;
103 5         30 $self->_clear_buffer();
104             return
105 5         17 }
106              
107             =head2 next
108              
109             Returns a hash of the next line of postfix log data.
110              
111             Hash keys are:
112              
113             delay_before_queue, delay_connect_setup, delay_in_queue,
114             delay_message_transmission, from, host, id, msgid, pid, program,
115             relay, size, status, text, timestamp, to, delay, connect,
116             disconnect, previous_host, previous_host_name, previous_host_ip
117              
118             All keys are guaranteed to be present. 'program', 'pid', 'host', 'timestamp',
119             'id' and 'text' are guaranteed to have a value. 'connect' and 'disconnect' are
120             boolean: true if the line is the relevant type of line, false otherwise.
121              
122             The 'text' key will have all of the log text B the standard Postfix
123             header. (All of which is in the other keys that are required to have a value.)
124              
125             =cut
126              
127             sub _parse_next_line {
128             # my ($self) = @_; # Saves a couple of microseconds per call not to use $self.
129             # Given the _extreme_ amounts this method is called,
130             # I thought it worth the trade-off. $_[0] == $self
131              
132             # The hash we will return.
133 2408     2408   5652 my %line_info = ( program => '' );
134              
135             # Some temp variables.
136 2408         2762 my $line;
137             my @line_data;
138              
139             # In a mixed-log enviornment, we can't count on any particular line being
140             # something we can parse. Keep going until we can.
141 2408         5713 while ( $line_info{program} !~ m/postfix/ ) {
142             # Read the line.
143 2408 100       6213 $line = $_[0]->_get_data_line() or return undef;
144              
145             # Start parsing.
146 2404         72786 @line_data = split ' ', $line, 7;
147              
148 4     4   27 no warnings qw(uninitialized);
  4         5  
  4         3583  
149             # Program name and pid.
150 2404         18594 ($line_info{program}, $line_info{pid}) = $line_data[4] =~ m/([^[]+)\[(\d+)\]/;
151             }
152              
153             # First few fields are the date. Convert back to Unix format...
154             { # We don't need all these temp variables hanging around.
155 2404         2728 my ($log_hour, $log_minutes, $log_seconds) = split /:/, $line_data[2];
  2404         6504  
156 2404 100       3299 if (!defined($log_info{${$_[0]}}->{year}) ) {
  2404         6622  
157 129         2462 $line_info{timestamp} = timelocal($log_seconds, $log_minutes, $log_hour, $line_data[1], $MONTH_NUMBER{$line_data[0]}, $CURR_DATE[5]);
158             }
159             else {
160 2275         4067 $line_info{timestamp} = timelocal($log_seconds, $log_minutes, $log_hour, $line_data[1], $MONTH_NUMBER{$line_data[0]}, $log_info{${$_[0]}}->{year});
  2275         41998  
161             }
162             }
163              
164             # Machine Hostname
165 2404         59462 $line_info{host} = $line_data[3];
166              
167             # Connection ID
168 2404 100       8264 if ( $line_data[5] =~ m/([^:]+):/ ) {
169 1997         4983 $line_info{id} = $1;
170             }
171             else {
172 407         666 $line_info{id} = undef;
173             }
174              
175             # The full rest is given as text.
176 2404 100       4249 if (defined($line_info{id})) {
177 1997         3300 $line_info{text} = $line_data[6];
178             }
179             else {
180 407         1126 $line_info{text} = join ' ', @line_data[5..$#line_data];
181             }
182 2404         4301 chomp $line_info{text};
183              
184             # Stage two of parsing.
185             # (These may or may not return any info...)
186              
187             # To address
188 2404         7417 @{$line_info{to}} = $line_info{text} =~ m/\bto=([^,]+),/g;
  2404         5665  
189              
190 2404 100       5241 if ( defined($line_info{to}[0]) ) {
191             # Relay
192 1158         5837 ($line_info{relay}) = $line_info{text} =~ m/\brelay=([^,]+),/;
193              
194             # Delays
195             ($line_info{delay_before_queue}, $line_info{delay_in_queue}, $line_info{delay_connect_setup}, $line_info{delay_message_transmission} )
196 1158         7596 = $line_info{text} =~ m{\bdelays=([^/]+)/([^/]+)/([^/]+)/([^,]+),};
197 1158         5041 ($line_info{delay}) = $line_info{text} =~ m/\bdelay=([\d.]+),/;
198              
199             # Status
200 1158         4766 ($line_info{status}) = $line_info{text} =~ m/\bstatus=(.+)\Z/;
201              
202 1158         4896 @line_info{'from', 'size', 'msgid', 'connect', 'disconnect', 'previous_host'
203             , 'previous_host_name', 'previous_host_ip' } = undef;
204             }
205             else {
206             # From address
207 1246         3900 ($line_info{from}) = $line_info{text} =~ m/\bfrom=([^,]+),/;
208              
209             # Size
210 1246         2856 ($line_info{size}) = $line_info{text} =~ m/\bsize=([^,]+),/;
211              
212             # Message ID
213 1246         2766 ($line_info{msgid}) = $line_info{text} =~ m/\bmessage-id=(.+)$/;
214              
215             # Connect (Boolean)
216 1246         3295 $line_info{connect} = $line_info{text} =~ m/\bconnect from/;
217              
218             # Disconnect (Boolean)
219 1246         2788 $line_info{disconnect} = $line_info{text} =~ m/\bdisconnect from/;
220              
221             # Remote host info. (Only if above.)
222 1246 100 66     3917 if ( $line_info{connect} || $line_info{disconnect} ) {
223 399         1599 ($line_info{previous_host}) = $line_info{text} =~ m/connect from (\S+)/;
224             ($line_info{previous_host_name}, $line_info{previous_host_ip})
225 399         2078 = $line_info{previous_host} =~ m/([^[]+)\[([^\]]+)\]/;
226             }
227             else {
228 847         1915 @line_info{'previous_host', 'previous_host_name', 'previous_host_ip'} = undef;
229             }
230              
231 1246         4417 @line_info{'relay', 'status', 'delay_before_queue', 'delay_in_queue'
232             , 'delay_connect_setup', 'delay_message_transmission', 'delay'}
233             = undef;
234             }
235              
236             # Return the data.
237 2404         10493 return \%line_info;
238             }
239              
240             =head1 BUGS
241              
242             None known at the moment.
243              
244             =head1 REQUIRES
245              
246             L, L, L, L,
247             L
248              
249             =head1 AUTHOR
250              
251             Daniel T. Staal
252              
253             DStaal@usa.net
254              
255             =head1 SEE ALSO
256              
257             L, for the main documentation on this module set.
258              
259             =head1 HISTORY
260              
261             April 17, 2009 (1.5.1) - No longer uses C<_set_current_position_as_next_line>,
262             instead lets Mail::Log::Parse manage automatically. (Requires 1.4.0.)
263              
264             April 9, 2009 (1.5.0) - Now reads the connecting host from the 'connect' and
265             'disconnect' lines in the log.
266              
267             Feb 27, 2009 (1.4.12) - Quieted an occasional error, if the log line doesn't
268             have the standard Postfix format.
269              
270             Dec 23, 2008 (1.4.11) - Further speedups. Now requires Mail::Log::Parse of at
271             least version 1.3.0.
272              
273             Dec 09, 2008 (1.4.10) - Profiled code, did some speedups. Added dependency on
274             Memoize: For large logs this is a massive speedup. For extremely sparse logs
275             it may not be, but sparse logs are likely to be small.
276              
277             Nov 28, 2008 - Switched 'total_delay' to slightly more universal 'delay'.
278             Sped up some regexes.
279              
280             Nov 11, 2008 - Switched to using the bufferable C<_parse_next_line> instead of
281             the unbuffered C.
282              
283             Nov 6, 2008 - Added C and alternate year handling, in case we aren't
284             dealing with this year's logs. (From the todo list.)
285              
286             Oct 24, 2008 - Added 'connect' and 'disconnect' members to the return hash.
287              
288             Oct 6, 2008 - Initial version.
289              
290             =head1 COPYRIGHT and LICENSE
291              
292             Copyright (c) 2008 Daniel T. Staal. All rights reserved.
293             This program is free software; you can redistribute it and/or
294             modify it under the same terms as Perl itself.
295              
296             This copyright will expire in 30 years, or 5 years after the author's
297             death, whichever is longer.
298              
299             =cut
300              
301             # End module package.
302             }
303             1;