File Coverage

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


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   97652 use strict;
  4         21  
  4         213  
25 4     4   23 use warnings;
  4         9  
  4         145  
26 4     4   20 use Scalar::Util qw(refaddr);
  4         9  
  4         569  
27 4     4   1232 use Time::Local;
  4         1780  
  4         252  
28 4     4   1694 use Mail::Log::Parse 1.0400;
  4         108  
  4         183  
29 4     4   22 use Mail::Log::Exceptions;
  4         7  
  4         105  
30 4     4   19 use base qw(Mail::Log::Parse Exporter);
  4         7  
  4         381  
31              
32 4     4   6968 use Memoize;
  4         12326  
  4         292  
33             memoize('timelocal');
34              
35             BEGIN {
36 4     4   31 use Exporter ();
  4         7  
  4         83  
37 4     4   22 use vars qw($VERSION);
  4         9  
  4         167  
38 4     4   1419 $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   10570 my ($self) = @_;
73            
74 7         26 delete $log_info{$$self};
75            
76 7         65 $self->SUPER::DESTROY();
77            
78 7         284 return;
79             }
80              
81             sub new {
82 7     7 1 392980 my ($class, $parameters_ref) = @_;
83              
84 7         55 my $self = $class->SUPER::new($parameters_ref);
85              
86 5 100       23 if (defined($parameters_ref->{year})) {
87 1         6 $self->set_year($parameters_ref->{year});
88             }
89              
90 5         14 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 22694 my ($self, $year) = @_;
102 5         36 $log_info{refaddr $self}->{year} = $year;
103 5         59 $self->_clear_buffer();
104             return
105 5         12 }
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   7172 my %line_info = ( program => '' );
134              
135             # Some temp variables.
136 2408         3026 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         6115 while ( $line_info{program} !~ m/postfix/ ) {
142             # Read the line.
143 2408 100       8226 $line = $_[0]->_get_data_line() or return undef;
144              
145             # Start parsing.
146 2404         95145 @line_data = split ' ', $line, 7;
147              
148 4     4   33 no warnings qw(uninitialized);
  4         14  
  4         3512  
149             # Program name and pid.
150 2404         37861 ($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         3090 my ($log_hour, $log_minutes, $log_seconds) = split /:/, $line_data[2];
  2404         7643  
156 2404 100       2997 if (!defined($log_info{${$_[0]}}->{year}) ) {
  2404         10806  
157 129         3051 $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         4328 $line_info{timestamp} = timelocal($log_seconds, $log_minutes, $log_hour, $line_data[1], $MONTH_NUMBER{$line_data[0]}, $log_info{${$_[0]}}->{year});
  2275         67895  
161             }
162             }
163              
164             # Machine Hostname
165 2404         73797 $line_info{host} = $line_data[3];
166              
167             # Connection ID
168 2404 100       9144 if ( $line_data[5] =~ m/([^:]+):/ ) {
169 1997         5532 $line_info{id} = $1;
170             }
171             else {
172 407         676 $line_info{id} = undef;
173             }
174              
175             # The full rest is given as text.
176 2404 100       4192 if (defined($line_info{id})) {
177 1997         3847 $line_info{text} = $line_data[6];
178             }
179             else {
180 407         1418 $line_info{text} = join ' ', @line_data[5..$#line_data];
181             }
182 2404         6105 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         9249 @{$line_info{to}} = $line_info{text} =~ m/\bto=([^,]+),/g;
  2404         6124  
189              
190 2404 100       5329 if ( defined($line_info{to}[0]) ) {
191             # Relay
192 1158         6976 ($line_info{relay}) = $line_info{text} =~ m/\brelay=([^,]+),/;
193              
194             # Delays
195 1158         10278 ($line_info{delay_before_queue}, $line_info{delay_in_queue}, $line_info{delay_connect_setup}, $line_info{delay_message_transmission} )
196             = $line_info{text} =~ m{\bdelays=([^/]+)/([^/]+)/([^/]+)/([^,]+),};
197 1158         6014 ($line_info{delay}) = $line_info{text} =~ m/\bdelay=([\d.]+),/;
198              
199             # Status
200 1158         5308 ($line_info{status}) = $line_info{text} =~ m/\bstatus=(.+)\Z/;
201              
202 1158         6504 @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         7924 ($line_info{from}) = $line_info{text} =~ m/\bfrom=([^,]+),/;
208              
209             # Size
210 1246         3091 ($line_info{size}) = $line_info{text} =~ m/\bsize=([^,]+),/;
211              
212             # Message ID
213 1246         2999 ($line_info{msgid}) = $line_info{text} =~ m/\bmessage-id=(.+)$/;
214              
215             # Connect (Boolean)
216 1246         3301 $line_info{connect} = $line_info{text} =~ m/\bconnect from/;
217              
218             # Disconnect (Boolean)
219 1246         2894 $line_info{disconnect} = $line_info{text} =~ m/\bdisconnect from/;
220              
221             # Remote host info. (Only if above.)
222 1246 100 100     8033 if ( $line_info{connect} || $line_info{disconnect} ) {
223 399         1788 ($line_info{previous_host}) = $line_info{text} =~ m/connect from (\S+)/;
224 399         2500 ($line_info{previous_host_name}, $line_info{previous_host_ip})
225             = $line_info{previous_host} =~ m/([^[]+)\[([^\]]+)\]/;
226             }
227             else {
228 847         2441 @line_info{'previous_host', 'previous_host_name', 'previous_host_ip'} = undef;
229             }
230              
231 1246         6100 @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         11958 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;