#!/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 . 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}; $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; } sub open_compressed_file { my ($file,$encoding) = @_; $encoding //= ':encoding(UTF-8)'; my $fh; if (not defined $file) { $fh = \*STDIN; binmode($fh,':encoding(UTF-8)'); return $fh; } my $mode = "<$encoding"; my @opts; if ($file =~ /\.gz$/) { $mode = "-|$encoding"; push @opts,'gzip','-dc'; } if ($file =~ /\.xz$/) { $mode = "-|$encoding"; push @opts,'xz','-dc'; } if ($file =~ /\.bz2$/) { $mode = "-|$encoding"; push @opts,'bzip2','-dc'; } open($fh,$mode,@opts,$file); return $fh; } my %postfix_ids; for my $file (@ARGV) { my $fh = open_compressed_file($file); 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 # \s(?\S+)# hostname # \spostfix\/(?[^[]+) #process # \[(?[^\]]+)\]:\s #pid # (?[A-F0-9]+)\:\s # (?.+) # /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__