File Coverage

blib/lib/ApacheLog/Parser.pm
Criterion Covered Total %
statement 30 55 54.5
branch 2 12 16.6
condition 0 10 0.0
subroutine 8 11 72.7
pod 2 2 100.0
total 42 90 46.6


line stmt bran cond sub pod time code
1             package ApacheLog::Parser;
2             $VERSION = v0.0.1;
3              
4 1     1   898 use warnings;
  1         3  
  1         39  
5 1     1   6 use strict;
  1         3  
  1         35  
6 1     1   22 use Carp;
  1         3  
  1         126  
7              
8             =head1 NAME
9              
10             ApacheLog::Parser - parse apache 'combined' log format
11              
12             =head1 SYNOPSIS
13              
14             use ApacheLog::Parser qw(parse_line);
15             # imports the field constants too
16              
17             my %visitors;
18             while(my $line = <$fh>) {
19             chomp($line);
20             my $ref = parse_line($line);
21             ($visitors{$ref->[client]} ||= 0)++;
22             }
23             no ApacheLog::Parser; # cleans-out the constants
24              
25             print join("\n ", 'visitors:',
26             map({$_ . ': ' . $visitors{$_}} keys(%visitors))), "\n";
27              
28              
29             =cut
30              
31             my @fields;
32             BEGIN {
33 1     1   56 @fields = qw(
34             client
35             login
36             dtime
37             request
38             file
39             params
40             proto
41             code
42             bytes
43             refer
44             agent
45             );
46             }
47 1     1   7 use constant ({map({$fields[$_] => $_} 0..$#fields)});
  1         2  
  1         4  
  11         246  
48              
49             ########################################################################
50             # I-hate-exporter overhead
51             my %exported;
52             my $do_export = sub {
53             my $package = shift;
54             my ($caller, $function) = @_;
55              
56             my $track = $exported{$package} ||= {};
57             $track = $track->{$caller} ||= {};
58              
59             $track->{$function} = 1;
60 1     1   6 no strict 'refs';
  1         2  
  1         308  
61             *{$caller . '::' . $function} = $package->can($function) or
62             croak("cannot $function");
63             };
64              
65             sub import {
66 1     1   2 my $package = shift;
67 1         2 my $caller = caller;
68 1         3 my %args = map({$_ => 1} @_);
  2         7  
69              
70             # DWIM bits
71 1 50       6 if($args{parse_line}) {
72 1         2 $args{':fields'} = 1;
73             }
74              
75             # exports
76 1 50       5 if(delete($args{':fields'})) {
77 1         5 $package->$do_export($caller, $_) for(@fields);
78             }
79              
80 1         4 foreach my $func (keys(%args)) {
81 1         2 $package->$do_export($caller, $func);
82             }
83             }
84              
85             =head2 unimport
86              
87             Allows 'no ApacheLog::Parser' to cleanup your namespace.
88              
89             =cut
90              
91             sub unimport {
92 0     0     my $package = shift;
93 0           my $caller = caller;
94              
95 0   0       my $track = $exported{$package} ||= {};
96 0   0       $track = $track->{$caller} ||= {};
97 0           foreach my $func (keys(%$track)) {
98 1     1   6 no strict 'refs';
  1         13  
  1         734  
99 0           delete(${$caller . '::'}{$func});
  0            
100             }
101             }
102             ########################################################################
103              
104             # TODO document this as an interface?
105             our $regexp = qr/^
106             ([^ ]+)\ +([^ ]+)\ +([^\[]+)\ + # client, ruser, login
107             \[([^\]]+)\]\ + # date
108             "(.*)"\ +(\d+)\ +(\d+|-)\ + # req, code, bytes
109             "(.*)"\ +"(.*)" # refer, agent
110             $/x;
111              
112             =head2 parse_line
113              
114             Assumes an already chomp()'d $line.
115              
116             my $array_ref = parse_line($line);
117              
118             =cut
119              
120             sub parse_line {
121 0     0 1   my ($line) = @_;
122              
123 0           my @v;
124             my $req;
125              
126 0 0         $line =~ $regexp or die "failed to parse $line";
127 0           (@v[client, login, dtime], $req, @v[code, bytes, refer, agent]) =
128             ($1, $3, $4, $5, $6, $7, $8, $9);
129              
130 0 0         $v[code] or die "no code in $line (@v)";
131              
132 0           $req =~ s/^(?:([A-Z]+) +)?//;
133 0   0       $v[request] = $1 || ''; # ouch, a non-request (telnet) hack
134             # just tear this off the end
135 0 0         $v[proto] = ($req =~ s# +(HTTP/\d+\.\d+)$##) ? $1 : '';
136              
137 0           @v[file, params] = split(/\?/, $req, 2);
138 0   0       defined($v[$_]) or $v[$_] = '' for(file, params);
139 0           $v[params] =~ s/\\"/"/g;
140              
141             ($v[$_] eq '-') and $v[$_] = ''
142 0   0       for(login, request, code, refer, agent);
143 0 0         $v[bytes] = 0 if($v[bytes] eq '-');
144              
145 0           return(\@v);
146             } # end subroutine parse_line definition
147             ########################################################################
148              
149             =head2 parse_line_to_hash
150              
151             Is a little more elegant interface than the array-ref and constants used
152             in parse_line(), but you pay dearly for passing-around those hash keys.
153             Fun for one-off stuff, but not recommended for heavy lifting.
154              
155             my %hash = parse_line_to_hash($line);
156              
157             =cut
158              
159             sub parse_line_to_hash {
160 0     0 1   my @keys = @fields;
161 0           return(map({shift(@keys) => $_} @{parse_line($_[0])}));
  0            
  0            
162             } # end subroutine parse_line_to_hash definition
163             ########################################################################
164              
165             =head1 AUTHOR
166              
167             Eric Wilhelm @
168              
169             http://scratchcomputing.com/
170              
171             =head1 BUGS
172              
173             If you found this module on CPAN, please report any bugs or feature
174             requests through the web interface at L. I will be
175             notified, and then you'll automatically be notified of progress on your
176             bug as I make changes.
177              
178             If you pulled this development version from my /svn/, please contact me
179             directly.
180              
181             =head1 COPYRIGHT
182              
183             Copyright (C) 2007 Eric L. Wilhelm, All Rights Reserved.
184              
185             =head1 NO WARRANTY
186              
187             Absolutely, positively NO WARRANTY, neither express or implied, is
188             offered with this software. You use this software at your own risk. In
189             case of loss, no person or entity owes you anything whatsoever. You
190             have been warned.
191              
192             =head1 LICENSE
193              
194             This program is free software; you can redistribute it and/or modify it
195             under the same terms as Perl itself.
196              
197             =cut
198              
199             # vi:ts=2:sw=2:et:sta
200             1;