File Coverage

blib/lib/Apache/LogFormat/Compiler.pm
Criterion Covered Total %
statement 55 61 90.1
branch 12 16 75.0
condition 6 6 100.0
subroutine 12 14 85.7
pod 2 6 33.3
total 87 103 84.4


line stmt bran cond sub pod time code
1             package Apache::LogFormat::Compiler;
2              
3 5     5   360397 use strict;
  5         6  
  5         121  
4 5     5   18 use warnings;
  5         5  
  5         90  
5 5     5   126 use 5.008001;
  5         16  
6 5     5   16 use Carp;
  5         5  
  5         235  
7 5     5   2296 use POSIX::strftime::Compiler qw//;
  5         40270  
  5         171  
8             use constant {
9 5         4197 ENVS => 0,
10             RES => 1,
11             LENGTH => 2,
12             REQTIME => 3,
13             TIME => 4,
14 5     5   28 };
  5         6  
15              
16             our $VERSION = '0.34';
17              
18             # copy from Plack::Middleware::AccessLog
19             our %formats = (
20             common => '%h %l %u %t "%r" %>s %b',
21             combined => '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-agent}i"',
22             );
23              
24             sub _safe {
25 9     9   10 my $string = shift;
26 9 50       15 return unless defined $string;
27 9         11 $string =~ s/([^[:print:]])/"\\x" . unpack("H*", $1)/eg;
  0         0  
28 9         105 return $string;
29             }
30              
31             sub _string {
32 14     14   2088 my $string = shift;
33 14 100       118 return '-' if ! defined $string;
34 8 50       13 return '-' if ! length $string;
35 8         12 $string =~ s/([^[:print:]])/"\\x" . unpack("H*", $1)/eg;
  0         0  
36 8         30 return $string;
37             }
38              
39             sub header_get {
40 3     3 0 176 my ($headers, $key) = @_;
41 3         4 $key = lc $key;
42 3         6 my @headers = @$headers; # copy
43 3         2 my $value;
44 3         9 while (my($hdr, $val) = splice @headers, 0, 2) {
45 4 100       9 if ( lc $hdr eq $key ) {
46 3         3 $value = $val;
47 3         3 last;
48             }
49             }
50 3         11 return $value;
51             }
52              
53             my $psgi_reserved = { CONTENT_LENGTH => 1, CONTENT_TYPE => 1 };
54              
55             my $block_handler = sub {
56             my($block, $type, $extra) = @_;
57             my $cb;
58             if ($type eq 'i') {
59             $block =~ s/-/_/g;
60             $block = uc($block);
61             $block = "HTTP_${block}" unless $psgi_reserved->{$block};
62             $cb = q!_string($_[ENVS]->{'!.$block.q!'})!;
63             } elsif ($type eq 'o') {
64             $cb = q!_string(header_get($_[RES]->[1],'!.$block.q!'))!;
65             } elsif ($type eq 't') {
66             $cb = q!"[" . POSIX::strftime::Compiler::strftime('!.$block.q!', @lt) . "]"!;
67             } elsif (exists $extra->{$type}) {
68             $cb = q!_string($extra_block_handlers->{'!.$type.q!'}->('!.$block.q!',$_[ENVS],$_[RES],$_[LENGTH],$_[REQTIME]))!;
69             } else {
70             Carp::croak("{$block}$type not supported");
71             $cb = "-";
72             }
73             return q|! . | . $cb . q|
74             . q!|;
75             };
76              
77             our %char_handler = (
78             '%' => q!'%'!,
79             h => q!($_[ENVS]->{REMOTE_ADDR} || '-')!,
80             l => q!'-'!,
81             u => q!($_[ENVS]->{REMOTE_USER} || '-')!,
82             t => q!'[' . $t . ']'!,
83             r => q!_safe($_[ENVS]->{REQUEST_METHOD}) . " " . _safe($_[ENVS]->{REQUEST_URI}) .
84             " " . $_[ENVS]->{SERVER_PROTOCOL}!,
85             s => q!$_[RES]->[0]!,
86             b => q!(defined $_[LENGTH] ? $_[LENGTH] : '-')!,
87             T => q!(defined $_[REQTIME] ? int($_[REQTIME]/1_000_000) : '-')!,
88             D => q!(defined $_[REQTIME] ? $_[REQTIME] : '-')!,
89             v => q!($_[ENVS]->{SERVER_NAME} || '-')!,
90             V => q!($_[ENVS]->{HTTP_HOST} || $_[ENVS]->{SERVER_NAME} || '-')!,
91             p => q!$_[ENVS]->{SERVER_PORT}!,
92             P => q!$$!,
93             m => q!_safe($_[ENVS]->{REQUEST_METHOD})!,
94             U => q!_safe($_[ENVS]->{PATH_INFO})!,
95             q => q!(($_[ENVS]->{QUERY_STRING} ne '') ? '?' . _safe($_[ENVS]->{QUERY_STRING}) : '' )!,
96             H => q!$_[ENVS]->{SERVER_PROTOCOL}!,
97              
98             );
99              
100             my $char_handler = sub {
101             my ($char, $extra) = @_;
102             my $cb = $char_handler{$char};
103             if (!$cb && exists $extra->{$char}) {
104             $cb = q!_string($extra_char_handlers->{'!.$char.q!'}->($_[ENVS],$_[RES],$_[LENGTH],$_[REQTIME]))!;
105             }
106             unless ($cb) {
107             Carp::croak "\%$char not supported.";
108             return "-";
109             }
110             q|! . | . $cb . q|
111             . q!|;
112             };
113              
114             sub new {
115 17     17 1 18063 my $class = shift;
116              
117 17   100     51 my $fmt = shift || "combined";
118 17 100       45 $fmt = $formats{$fmt} if exists $formats{$fmt};
119              
120 17         31 my %opts = @_;
121              
122 17   100     137 my ($code_ref, $code) = compile($fmt, $opts{block_handlers} || {}, $opts{char_handlers} || {});
      100        
123 17         73 bless [$code_ref, $code], $class;
124             }
125              
126             sub compile {
127 17     17 0 23 my $fmt = shift;
128 17         21 my $extra_block_handlers = shift;
129 17         13 my $extra_char_handlers = shift;
130 17         37 $fmt =~ s/!/\\!/g;
131 17         127 $fmt =~ s!
132             (?:
133             \%\{(.+?)\}([a-zA-Z]) |
134             \%(?:[<>])?([a-zA-Z\%])
135             )
136 53 100       139 ! $1 ? $block_handler->($1, $2, $extra_block_handlers) : $char_handler->($3, $extra_char_handlers) !egx;
137            
138 17         71 my @abbr = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
139 17         20 my $c = {};
140 17         47 $fmt = q~sub {
141             $_[TIME] = time() if ! defined $_[TIME];
142             my @lt = localtime($_[TIME]);
143             if ( ! exists $c->{tz_cache} || ! exists $c->{isdst_cache} || $lt[8] != $c->{isdst_cache} ) {
144             $c->{tz_cache} = POSIX::strftime::Compiler::strftime('%z',@lt);
145             $c->{isdst_cache} = $lt[8];
146             }
147             my $t = sprintf '%02d/%s/%04d:%02d:%02d:%02d %s', $lt[3], $abbr[$lt[4]], $lt[5]+1900,
148             $lt[2], $lt[1], $lt[0], $c->{tz_cache};
149             q!~ . $fmt . q~!
150             }~;
151 17         3605 my $code_ref = eval $fmt; ## no critic
152 17 50       46 die $@ . "\n===\n" . $fmt if $@;
153 17 50       68 wantarray ? ($code_ref, $fmt) : $code_ref;
154             }
155              
156             sub log_line {
157 47     47 1 77940 my $self = shift;
158 47         1303 $self->[0]->(@_) . "\n";
159             }
160              
161             sub code {
162 0     0 0   my $self = shift;
163 0           $self->[1];
164             }
165              
166             sub code_ref {
167 0     0 0   my $self = shift;
168 0           $self->[0];
169             }
170              
171             1;
172             __END__