File Coverage

blib/lib/AnyEvent/Postfix/Logs.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package AnyEvent::Postfix::Logs;
2              
3 1     1   19867 use warnings;
  1         2  
  1         27  
4 1     1   5 use strict;
  1         2  
  1         56  
5              
6             =head1 NAME
7              
8             AnyEvent::Postfix::Logs - Event based parsin of Postfix log files
9              
10             =cut
11              
12             our $VERSION = '0.01';
13              
14              
15             =head1 SYNOPSIS
16              
17             use AnyEvent::Postfix::Logs;
18              
19             my $cv = AnyEvent->condvar;
20              
21             AnyEvent::Postfix::Logs->new(
22             sources => [ \*STDIN ],
23             on_mail => sub { say "Mail from $_[0]->{from} to ", join(", ", @{ $_[0]->{to} } ) },
24             on_finish => sub { say "No more mail"; $cv->send() },
25             on_error => sub { croak $_[0] },
26             );
27              
28             # do some more stuff
29              
30             $cv->recv;
31             ...
32              
33             =head1 DESCRIPTION
34              
35             This module implement parsing of postfix log files from multiple sources. Each
36             time a mail is removed from postfix' queue a callback is invoked with
37             collected information about the mail.
38              
39             B This module is developen on an need to do basis for ad
40             hoc-problems. Do not expect it to be a complete implementation, but if you
41             need adtional features pleaes submit a bug.
42              
43             =head1 METHODS
44              
45             =head2 new
46              
47             Creates a new instance of a C module
48              
49             =head3 PARAMETERS
50              
51             =over 4
52              
53             =item sources (array of sources)
54              
55             Valid sources are perl file handles or names of log files
56              
57             =item on_mail (callback)
58              
59             Reference to a handler to called for each mail fully completed by postfix. It
60             vill be invoked as
61              
62             $on_mail->($mail)
63              
64             where C<$mail> is a hashref with keys like C (string), C (array of
65             strings), C
66             C (string).
67              
68             =item on_finish (callback)
69              
70             Reference to a handler called when all sources are depleted. It will be
71             invoked as
72              
73             $on_finish->()
74              
75             =item on_error (callback)
76              
77             Reference to a handler called when an error occurs. It will be invoked as
78              
79             $on_error->($message)
80              
81             Default is to croak
82              
83             =back
84              
85             =head2 add_source
86              
87             Add a list of additional sources
88              
89             =cut
90              
91 1     1   391 use AnyEvent;
  0            
  0            
92             use AnyEvent::Handle;
93              
94             use Carp;
95             use Scalar::Util qw(refaddr);
96              
97             sub new {
98             my ($class, %args) = @_;
99             my $self = bless { }, $class;
100              
101             $self->{on_mail} = delete $args{on_mail} || sub { };
102             $self->{on_finish} = delete $args{on_finish} || sub { };
103             $self->{on_error} = delete $args{on_error} || sub { croak $_[0] };
104              
105             $self->{messages} = { };
106             $self->add_source( @{ delete $args{sources} || [ ] } );
107              
108             return $self;
109             }
110              
111             sub add_source {
112             my ($self, @sources) = @_;
113              
114             for my $file ( @sources ) {
115             unless ( ref $file ) {
116             # Assume it's a file name
117             my $filename = $file;
118            
119             $file = undef;
120             open $file, "<", $filename
121             or $self->{on_error}->("Couldn't open $filename: $!");
122             }
123            
124             my $handle = AnyEvent::Handle->new (
125             fh => $file,
126             );
127              
128             $handle->push_read( line => sub { $self->parseline( @_ ) } );
129             $handle->on_error( sub {
130             my ($handle, $fatal, $message) = @_;
131              
132             delete $self->{handles}->{refaddr $handle} if $fatal;
133             $self->{on_error}->($message) unless eof( $handle->fh );
134             $self->{on_finish}->() unless keys %{ $self->{handles} };
135             });
136              
137             $self->{handles}->{refaddr $handle} = $handle;
138             }
139              
140             return 1;
141             }
142              
143             sub parseline {
144             my ($self, $handle, $line) = @_;
145              
146             if ( $line =~ m!^(\w\w\w \d\d \d\d:\d\d:\d\d) (\w+) postfix/(\w+)\[\d+\]: ([0-9A-F]+): (.*)! ) {
147             my ($time, $server, $cmd, $id, $line) = ($1, $2, $3, $4, $5);
148              
149             # Find the message or create a fresh message hash
150             my $mail = $self->{messages}->{$server, $id} ||= { id => $id, time => $1, server => $2, to => [ ] };
151              
152             if ( $cmd eq 'qmgr' ) {
153              
154             if ($line =~ /^removed$/) {
155              
156             $self->{on_mail}->($mail);
157             delete $self->{messages}->{$server, $id};
158              
159             } else {
160             $mail->{from} = $1 if $line =~ /^from=<([^>]+)>/;
161             $mail->{size} = $1 if $line =~ /size=(\d+)/;
162             }
163              
164             } elsif ( $cmd eq 'cleanup' ) {
165             $mail->{msgid} = $1 if $line =~ /^message-id=(<[^>]+>)/;
166             } elsif ( $cmd eq 'virtual' ) {
167             push @{ $mail->{to} }, $1 if $line =~/^to=<([^>]+)>/;
168             $mail->{delay} = $1 if $line =~ /delay=(\d+\.\d+)/;
169             }
170              
171             }
172              
173             $handle->push_read( line => sub { $self->parseline( @_ ) } );
174             }
175              
176             =head1 AUTHOR
177              
178             Peter Makholm, C<< >>
179              
180             =head1 BUGS
181              
182             Please report any bugs or feature requests to C, or through
183             the web interface at L. I will be notified, and then you'll
184             automatically be notified of progress on your bug as I make changes.
185              
186              
187              
188              
189             =head1 SUPPORT
190              
191             You can find documentation for this module with the perldoc command.
192              
193             perldoc AnyEvent::Postfix::Logs
194              
195              
196             You can also look for information at:
197              
198             =over 4
199              
200             =item * RT: CPAN's request tracker
201              
202             L
203              
204             =item * AnnoCPAN: Annotated CPAN documentation
205              
206             L
207              
208             =item * CPAN Ratings
209              
210             L
211              
212             =item * Search CPAN
213              
214             L
215              
216             =back
217              
218              
219             =head1 ACKNOWLEDGEMENTS
220              
221              
222             =head1 COPYRIGHT & LICENSE
223              
224             Copyright 2009 Peter Makholm.
225              
226             This program is free software; you can redistribute it and/or modify it
227             under the terms of either: the GNU General Public License as published
228             by the Free Software Foundation; or the Artistic License.
229              
230             See http://dev.perl.org/licenses/ for more information.
231              
232              
233             =cut
234              
235             1; # End of AnyEvent::Postfix::Logs