--- /dev/null
+#!/usr/bin/perl
+# postfix_grep greps postfix logs and returns all lines matching pattern and queueid from matching lines
+# and is released under the terms of the GNU GPL version 3, or any
+# later version, at your option. See the file README and COPYING for
+# more information.
+# Copyright 2013 by Don Armstrong <don@donarmstrong.com>.
+
+
+use warnings;
+use strict;
+
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+postfix_grep - greps postfix logs and returns all lines matching pattern and queueid from matching lines
+
+=head1 SYNOPSIS
+
+postfix_grep [options] [regex] [mailfile]
+
+ Options:
+ --regex, -e regular expression to match
+ --debug, -d debugging level (Default 0)
+ --help, -h display this help
+ --man, -m display manual
+
+=head1 OPTIONS
+
+=over
+
+=item B<--regex, -e>
+
+Regular expression to match. May be specified multiple times, in which
+case a message must match all of them.
+
+If given, all remaining options are considered to be mail files.
+
+=item B<--debug, -d>
+
+Debug verbosity. (Default 0)
+
+=item B<--help, -h>
+
+Display brief usage information.
+
+=item B<--man, -m>
+
+Display this manual.
+
+=back
+
+=head1 EXAMPLES
+
+postfix_grep
+
+=cut
+
+
+use vars qw($DEBUG);
+
+my %options = (debug => 0,
+ help => 0,
+ man => 0,
+ );
+
+GetOptions(\%options,
+ 'regex|e=s@',
+ 'debug|d+','help|h|?','man|m');
+
+pod2usage() if $options{help};
+pod2usage({verbose=>2}) if $options{man};
+
+use IO::Uncompress::Gunzip;
+
+$DEBUG = $options{debug};
+
+if (not exists $options{regex}) {
+ $options{regex} = shift @ARGV if @ARGV;
+}
+$options{regex} = ref($options{regex})?$options{regex}:[$options{regex}];
+
+my @USAGE_ERRORS;
+if (not $options{regex}) {
+ push @USAGE_ERRORS,"You must supply a regex using -e or the command line";
+}
+
+pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
+
+my %regexes;
+for my $regex (@{$options{regex}}) {
+ $regexes{$regex} = qr/\Q$regex\E/;
+}
+
+
+my @line_buffer;
+my $buffer_size=10000;
+
+if (not @ARGV) {
+ # use undef as a special stdin holder
+ push @ARGV,undef;
+}
+
+my %postfix_ids;
+for my $file (@ARGV) {
+ my $fh = IO::Uncompress::Gunzip->new(defined $file ? $file:\*STDIN,
+ MultiStream => 1,
+ Transparent => 1,)
+ or die "IO::Uncompress::Gunzip failed: $IO::Uncompress::Gunzip::GunzipError";
+ while (<$fh>) {
+ chomp;
+ my $line = $_;
+ my $keep = 1;
+ for my $regex (keys %regexes) {
+ if ($line !~ $regexes{$regex}) {
+ $keep = 0;
+ }
+ }
+ if ($keep) {
+ my $queue_id = parse_queue_id($line);
+ $postfix_ids{$queue_id} = 1 if defined $queue_id;
+ }
+ output_and_update_buffer(\%regexes,\%postfix_ids,\@line_buffer,$buffer_size,$line);
+ }
+ while (@line_buffer) {
+ output_if_match(\%regexes,\%postfix_ids,\@line_buffer);
+ }
+ use Data::Dumper;
+ print Dumper(\%postfix_ids);
+ %postfix_ids = ();
+}
+
+sub parse_queue_id {
+ my ($line) = @_;
+# if ($line =~
+# /^(?<date>.+?) # date
+# \s(?<hostname>\S+)# hostname
+# \spostfix\/(?<process_name>[^[]+) #process
+# \[(?<pid>[^\]]+)\]:\s #pid
+# (?<queue_id>[A-F0-9]+)\:\s
+# (?<rest>.+)
+# /x) {
+# return $+{queue_id}
+# }
+ if ($line =~
+ /^(.+?) # date
+ \s(\S+)# hostname
+ \spostfix\/([^[]+) #process
+ \[([^\]]+)\]:\s #pid
+ ([A-F0-9]+)\:\s # queue_id
+ (.+) # rest
+ /x) {
+ return $5;
+ }
+ return undef;
+ # Apr 28 07:18:43 golf postfix/cleanup[27095]: 94E4F27038:
+}
+
+sub output_and_update_buffer{
+ my ($regexes,$postfix_ids,$lb,$lb_max_size,$line) = @_;
+ if (@{$lb} > $lb_max_size) {
+ output_if_match($regexes,$postfix_ids,$lb);
+ }
+ push @{$lb},$line;
+}
+
+sub output_if_match {
+ my ($regexes,$postfix_ids,$lb) = @_;
+
+ my $line = shift @{$lb};
+ return unless defined $line;
+ my $queue_id = parse_queue_id($line);
+ if (defined $queue_id and $postfix_ids->{$queue_id}) {
+ } else {
+ for my $regex (keys %{$regexes}) {
+ if ($line !~ $regexes->{$regex}) {
+ return;
+ }
+ }
+ }
+ print $line."\n";
+}
+
+__END__