]> git.donarmstrong.com Git - bin.git/blob - postfix_grep
stop using IO::Uncompress::Gunzip; it's slow
[bin.git] / postfix_grep
1 #!/usr/bin/perl
2 # postfix_grep greps postfix logs and returns all lines matching pattern and queueid from matching lines
3 # and is released under the terms of the GNU GPL version 3, or any
4 # later version, at your option. See the file README and COPYING for
5 # more information.
6 # Copyright 2013 by Don Armstrong <don@donarmstrong.com>.
7
8
9 use warnings;
10 use strict;
11
12 use Getopt::Long;
13 use Pod::Usage;
14
15 =head1 NAME
16
17 postfix_grep - greps postfix logs and returns all lines matching pattern and queueid from matching lines
18
19 =head1 SYNOPSIS
20
21 postfix_grep [options] [regex] [mailfile]
22
23  Options:
24   --regex, -e regular expression to match
25   --debug, -d debugging level (Default 0)
26   --help, -h display this help
27   --man, -m display manual
28
29 =head1 OPTIONS
30
31 =over
32
33 =item B<--regex, -e>
34
35 Regular expression to match. May be specified multiple times, in which
36 case a message must match all of them.
37
38 If given, all remaining options are considered to be mail files.
39
40 =item B<--debug, -d>
41
42 Debug verbosity. (Default 0)
43
44 =item B<--help, -h>
45
46 Display brief usage information.
47
48 =item B<--man, -m>
49
50 Display this manual.
51
52 =back
53
54 =head1 EXAMPLES
55
56 postfix_grep
57
58 =cut
59
60
61 use vars qw($DEBUG);
62
63 my %options = (debug           => 0,
64                help            => 0,
65                man             => 0,
66                );
67
68 GetOptions(\%options,
69            'regex|e=s@',
70            'debug|d+','help|h|?','man|m');
71
72 pod2usage() if $options{help};
73 pod2usage({verbose=>2}) if $options{man};
74
75 $DEBUG = $options{debug};
76
77 if (not exists $options{regex}) {
78     $options{regex} = shift @ARGV if @ARGV;
79 }
80 $options{regex} = ref($options{regex})?$options{regex}:[$options{regex}];
81
82 my @USAGE_ERRORS;
83 if (not $options{regex}) {
84      push @USAGE_ERRORS,"You must supply a regex using -e or the command line";
85 }
86
87 pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
88
89 my %regexes;
90 for my $regex (@{$options{regex}}) {
91     $regexes{$regex} = qr/\Q$regex\E/;
92 }
93
94
95 my @line_buffer;
96 my $buffer_size=10000;
97
98 if (not @ARGV) {
99     # use undef as a special stdin holder
100     push @ARGV,undef;
101 }
102
103 sub open_compressed_file {
104     my ($file,$encoding) = @_;
105     $encoding //= ':encoding(UTF-8)';
106     my $fh;
107     if (not defined $file) {
108         $fh = \*STDIN;
109         binmode($fh,':encoding(UTF-8)');
110         return $fh;
111     }
112     my $mode = "<$encoding";
113     my @opts;
114     if ($file =~ /\.gz$/) {
115         $mode = "-|$encoding";
116         push @opts,'gzip','-dc';
117     }
118     if ($file =~ /\.xz$/) {
119         $mode = "-|$encoding";
120         push @opts,'xz','-dc';
121     }
122     if ($file =~ /\.bz2$/) {
123         $mode = "-|$encoding";
124         push @opts,'bzip2','-dc';
125     }
126     open($fh,$mode,@opts,$file);
127     return $fh;
128 }
129
130 my %postfix_ids;
131 for my $file (@ARGV) {
132     my $fh = open_compressed_file($file);
133     while (<$fh>) {
134         chomp;
135         my $line = $_;
136         my $keep = 1;
137         for my $regex (keys %regexes) {
138             if ($line !~ $regexes{$regex}) {
139                 $keep = 0;
140             }
141         }
142         if ($keep) {
143             my $queue_id = parse_queue_id($line);
144             $postfix_ids{$queue_id} = 1 if defined $queue_id;
145         }
146         output_and_update_buffer(\%regexes,\%postfix_ids,\@line_buffer,$buffer_size,$line);
147     }
148     while (@line_buffer) {
149         output_if_match(\%regexes,\%postfix_ids,\@line_buffer);
150     }
151     use Data::Dumper;
152     print Dumper(\%postfix_ids);
153     %postfix_ids = ();
154 }
155
156 sub parse_queue_id {
157     my ($line) = @_;
158 #     if ($line =~
159 #         /^(?<date>.+?) # date
160 #          \s(?<hostname>\S+)# hostname
161 #          \spostfix\/(?<process_name>[^[]+) #process
162 #          \[(?<pid>[^\]]+)\]:\s #pid
163 #          (?<queue_id>[A-F0-9]+)\:\s
164 #          (?<rest>.+)
165 #         /x) {
166 #         return $+{queue_id}
167 #     }
168     if ($line =~
169         /^(.+?) # date
170          \s(\S+)# hostname
171          \spostfix\/([^[]+) #process
172          \[([^\]]+)\]:\s #pid
173          ([A-F0-9]+)\:\s # queue_id
174          (.+) # rest
175         /x) {
176         return $5;
177     }
178     return undef;
179     # Apr 28 07:18:43 golf postfix/cleanup[27095]: 94E4F27038:
180 }
181
182 sub output_and_update_buffer{
183     my ($regexes,$postfix_ids,$lb,$lb_max_size,$line) = @_;
184     if (@{$lb} > $lb_max_size) {
185         output_if_match($regexes,$postfix_ids,$lb);
186     }
187     push @{$lb},$line;
188 }
189
190 sub output_if_match {
191     my ($regexes,$postfix_ids,$lb) = @_;
192
193     my $line = shift @{$lb};
194     return unless defined $line;
195     my $queue_id = parse_queue_id($line);
196     if (defined $queue_id and $postfix_ids->{$queue_id}) {
197     } else {
198         for my $regex (keys %{$regexes}) {
199             if ($line !~ $regexes->{$regex}) {
200                 return;
201             }
202         }
203     }
204     print $line."\n";
205 }
206
207 __END__