line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mojolicious::Plugin::AccessLog; |
2
|
|
|
|
|
|
|
|
3
|
9
|
|
|
9
|
|
10012
|
use Mojo::Base 'Mojolicious::Plugin'; |
|
9
|
|
|
|
|
12
|
|
|
9
|
|
|
|
|
59
|
|
4
|
9
|
|
|
9
|
|
1636
|
use Mojo::IOLoop; |
|
9
|
|
|
|
|
12
|
|
|
9
|
|
|
|
|
79
|
|
5
|
|
|
|
|
|
|
|
6
|
9
|
|
|
9
|
|
169
|
use File::Spec; |
|
9
|
|
|
|
|
9
|
|
|
9
|
|
|
|
|
199
|
|
7
|
9
|
|
|
9
|
|
37
|
use IO::File; |
|
9
|
|
|
|
|
14
|
|
|
9
|
|
|
|
|
1888
|
|
8
|
9
|
|
|
9
|
|
50
|
use POSIX qw(setlocale strftime LC_ALL); |
|
9
|
|
|
|
|
10
|
|
|
9
|
|
|
|
|
77
|
|
9
|
9
|
|
|
9
|
|
743
|
use Scalar::Util qw(blessed reftype weaken); |
|
9
|
|
|
|
|
16
|
|
|
9
|
|
|
|
|
520
|
|
10
|
9
|
|
|
9
|
|
38
|
use Socket qw(inet_aton AF_INET); |
|
9
|
|
|
|
|
13
|
|
|
9
|
|
|
|
|
501
|
|
11
|
9
|
|
|
9
|
|
42
|
use Time::HiRes qw(gettimeofday tv_interval); |
|
9
|
|
|
|
|
11
|
|
|
9
|
|
|
|
|
78
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our $VERSION = '0.009'; |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
my $DEFAULT_FORMAT = 'common'; |
16
|
|
|
|
|
|
|
my %FORMATS = ( |
17
|
|
|
|
|
|
|
$DEFAULT_FORMAT => '%h %l %u %t "%r" %>s %b', |
18
|
|
|
|
|
|
|
combined => '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"', |
19
|
|
|
|
|
|
|
combinedio => '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i" %I %O', |
20
|
|
|
|
|
|
|
); |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
# some systems (Windows) don't support %z correctly |
23
|
|
|
|
|
|
|
my $TZOFFSET = strftime('%z', localtime) !~ /^[+-]\d{4}$/ && do { |
24
|
|
|
|
|
|
|
require Time::Local; |
25
|
|
|
|
|
|
|
my $t = time; |
26
|
|
|
|
|
|
|
my $d = (Time::Local::timegm(localtime($t)) - $t) / 60; |
27
|
|
|
|
|
|
|
sprintf '%+03d%02u', int($d / 60), $d % 60; |
28
|
|
|
|
|
|
|
}; |
29
|
|
|
|
|
|
|
# some systems (Windows) don't support %s |
30
|
|
|
|
|
|
|
my $NOEPOCHSECS = strftime('%s', localtime) !~ /^\d+$/; |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
sub register { |
33
|
9
|
|
|
9
|
1
|
578
|
my ($self, $app, $conf) = @_; |
34
|
9
|
|
66
|
|
|
49
|
my $log = $conf->{log} // $app->log->handle; |
35
|
9
|
|
|
|
|
135
|
my ($pkg, $f, $l) = caller 2; # :-/ |
36
|
9
|
|
|
|
|
22
|
my $fh; |
37
|
|
|
|
|
|
|
|
38
|
9
|
50
|
|
|
|
43
|
unless ($log) { # somebody cleared $app->log->handle? |
39
|
|
|
|
|
|
|
# Log a warning nevertheless - there might be an event handler. |
40
|
0
|
|
|
|
|
0
|
$app->log->warn(__PACKAGE__ . ': Log handle is not defined'); |
41
|
0
|
|
|
|
|
0
|
return; |
42
|
|
|
|
|
|
|
} |
43
|
|
|
|
|
|
|
|
44
|
9
|
|
100
|
|
|
49
|
my $reftype = reftype $log // ''; |
45
|
9
|
|
|
|
|
13
|
my $logger; |
46
|
|
|
|
|
|
|
|
47
|
9
|
100
|
66
|
|
|
71
|
if ($reftype eq 'GLOB') { |
|
|
100
|
66
|
|
|
|
|
|
|
100
|
33
|
|
|
|
|
|
|
50
|
|
|
|
|
|
48
|
4
|
|
|
|
|
6
|
$fh = $log; |
49
|
4
|
|
|
|
|
6
|
eval { $fh->autoflush(1) }; |
|
4
|
|
|
|
|
38
|
|
50
|
4
|
|
|
31
|
|
187
|
$logger = sub { $fh->print($_[0]) }; |
|
31
|
|
|
|
|
717
|
|
51
|
|
|
|
|
|
|
} |
52
|
|
|
|
|
|
|
elsif (blessed($log) and my $l = $log->can('print') || $log->can('info')) { |
53
|
2
|
|
|
13
|
|
9
|
$logger = sub { $l->($log, $_[0]) }; |
|
13
|
|
|
|
|
55
|
|
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
elsif ($reftype eq 'CODE') { |
56
|
2
|
|
|
|
|
3
|
$logger = $log; |
57
|
|
|
|
|
|
|
} |
58
|
|
|
|
|
|
|
elsif (defined $log and not ref $log) { |
59
|
1
|
50
|
|
|
|
12
|
File::Spec->file_name_is_absolute($log) |
60
|
|
|
|
|
|
|
or $log = $app->home->rel_file($log); |
61
|
|
|
|
|
|
|
|
62
|
1
|
50
|
|
|
|
7
|
$fh = IO::File->new($log, '>>') |
63
|
|
|
|
|
|
|
or die <<""; |
64
|
|
|
|
|
|
|
Can't open log file "$log": $! at $f line $l. |
65
|
|
|
|
|
|
|
|
66
|
1
|
|
|
|
|
342
|
$fh->autoflush(1); |
67
|
1
|
|
|
7
|
|
57
|
$logger = sub { $fh->print($_[0]) }; |
|
7
|
|
|
|
|
45
|
|
68
|
|
|
|
|
|
|
} |
69
|
|
|
|
|
|
|
else { |
70
|
0
|
|
|
|
|
0
|
$app->log->error(__PACKAGE__ . ': not a valid "log" value'); |
71
|
0
|
|
|
|
|
0
|
return; |
72
|
|
|
|
|
|
|
} |
73
|
|
|
|
|
|
|
|
74
|
9
|
50
|
|
|
|
28
|
if ($conf->{uname_helper}) { |
75
|
0
|
|
|
|
|
0
|
warn <<""; |
76
|
|
|
|
|
|
|
uname_helper is DEPRECATED in favor of \$c->req->env->{REMOTE_USER} at $f line $l. |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
|
79
|
0
|
|
|
|
|
0
|
my $helper_name = $conf->{uname_helper}; |
80
|
|
|
|
|
|
|
|
81
|
0
|
0
|
|
|
|
0
|
$helper_name = 'set_username' if $helper_name !~ /^[\_A-za-z]\w*$/; |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
$app->helper( |
84
|
0
|
|
|
0
|
|
0
|
$helper_name => sub { $_[0]->req->env->{REMOTE_USER} = $_[1] } |
85
|
0
|
|
|
|
|
0
|
); |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
|
88
|
9
|
|
|
|
|
15
|
my @handler; |
89
|
|
|
|
|
|
|
my $strftime = sub { |
90
|
69
|
|
|
69
|
|
241
|
my ($fmt, @time) = @_; |
91
|
69
|
50
|
|
|
|
188
|
$fmt =~ s/%z/$TZOFFSET/g if $TZOFFSET; |
92
|
69
|
50
|
|
|
|
153
|
$fmt =~ s/%s/time()/ge if $NOEPOCHSECS; |
|
0
|
|
|
|
|
0
|
|
93
|
69
|
|
|
|
|
730
|
my $old_locale = setlocale(LC_ALL); |
94
|
69
|
|
|
|
|
304
|
setlocale(LC_ALL, 'C'); |
95
|
69
|
|
|
|
|
1661
|
my $out = strftime($fmt, @time); |
96
|
69
|
|
|
|
|
288
|
setlocale(LC_ALL, $old_locale); |
97
|
69
|
|
|
|
|
466
|
return $out; |
98
|
9
|
|
|
|
|
32
|
}; |
99
|
9
|
|
66
|
|
|
43
|
my $format = $FORMATS{$conf->{format} // $DEFAULT_FORMAT}; |
100
|
9
|
|
|
|
|
12
|
my $safe_re; |
101
|
|
|
|
|
|
|
|
102
|
9
|
100
|
|
|
|
23
|
if ($format) { |
103
|
|
|
|
|
|
|
# Apache default log formats don't quote username, which might |
104
|
|
|
|
|
|
|
# have spaces. |
105
|
6
|
|
|
|
|
23
|
$safe_re = qr/([^[:print:]]|\s)/; |
106
|
|
|
|
|
|
|
} |
107
|
|
|
|
|
|
|
else { |
108
|
|
|
|
|
|
|
# For custom log format appropriate quoting is the user's responsibility. |
109
|
3
|
|
|
|
|
8
|
$format = $conf->{format}; |
110
|
|
|
|
|
|
|
} |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
# each handler is called with following parameters: |
113
|
|
|
|
|
|
|
# 0: $tx, 1: $tx->req, 2: $tx->res, 3: $tx->req->url, |
114
|
|
|
|
|
|
|
# 4: $request_start_time, 5: $process_time, 6: $bytes_in, 7: $bytes_out |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
my $block_handler = sub { |
117
|
22
|
|
|
22
|
|
66
|
my ($block, $type) = @_; |
118
|
|
|
|
|
|
|
|
119
|
104
|
|
100
|
|
|
1170
|
return sub { _safe($_[1]->headers->header($block) // '-') } |
120
|
22
|
100
|
|
|
|
90
|
if $type eq 'i'; |
121
|
|
|
|
|
|
|
|
122
|
18
|
|
50
|
|
|
309
|
return sub { $_[2]->headers->header($block) // '-' } |
123
|
7
|
100
|
|
|
|
13
|
if $type eq 'o'; |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
return sub { |
126
|
24
|
50
|
|
|
|
146
|
return $_[4][0] |
127
|
|
|
|
|
|
|
if $block eq 'sec'; |
128
|
24
|
50
|
|
|
|
43
|
return sprintf "%u%03u", $_[4][0], int($_[4][1] / 1000) |
129
|
|
|
|
|
|
|
if $block eq 'msec'; |
130
|
24
|
50
|
|
|
|
43
|
return sprintf "%u%06u", @{$_[4]} |
|
0
|
|
|
|
|
0
|
|
131
|
|
|
|
|
|
|
if $block eq 'usec'; |
132
|
24
|
100
|
|
|
|
106
|
return sprintf('%03u', $_[4][1] / 1000) |
133
|
|
|
|
|
|
|
if $block eq 'msec_frac'; |
134
|
18
|
50
|
|
|
|
190
|
return sprintf('%06u', $_[4][1]) |
135
|
|
|
|
|
|
|
if $block eq 'usec_frac'; |
136
|
18
|
|
|
|
|
529
|
return $strftime->($block, localtime($_[4][0])); |
137
|
|
|
|
|
|
|
} |
138
|
4
|
50
|
|
|
|
20
|
if $type eq 't'; |
139
|
|
|
|
|
|
|
|
140
|
0
|
|
0
|
|
|
0
|
return sub { _safe($_[1]->cookie($block // '')) } |
141
|
0
|
0
|
|
|
|
0
|
if $type eq 'C'; |
142
|
|
|
|
|
|
|
|
143
|
0
|
|
0
|
|
|
0
|
return sub { _safe($_[1]->env->{$block // ''}) } |
144
|
0
|
0
|
|
|
|
0
|
if $type eq 'e'; |
145
|
|
|
|
|
|
|
|
146
|
0
|
|
|
|
|
0
|
$app->log->error("{$block}$type not supported"); |
147
|
|
|
|
|
|
|
|
148
|
0
|
|
|
|
|
0
|
return '-'; |
149
|
9
|
|
|
|
|
31
|
}; |
150
|
|
|
|
|
|
|
|
151
|
9
|
50
|
|
14
|
|
22
|
my $servername_cb = sub { $_[3]->base->host || '-' }; |
|
14
|
|
|
|
|
213
|
|
152
|
9
|
50
|
|
58
|
|
19
|
my $remoteaddr_cb = sub { $_[0]->remote_address || '-' }; |
|
58
|
|
|
|
|
208
|
|
153
|
|
|
|
|
|
|
my %char_handler = ( |
154
|
|
|
|
|
|
|
'%' => '%', |
155
|
|
|
|
|
|
|
a => $remoteaddr_cb, |
156
|
7
|
|
50
|
7
|
|
141
|
A => sub { $_[0]->local_address // '-' }, |
157
|
|
|
|
|
|
|
b => sub { |
158
|
60
|
100
|
100
|
60
|
|
767
|
$_[7] && ($_[7] - $_[2]->header_size - $_[2]->start_line_size) || '-' |
159
|
|
|
|
|
|
|
}, |
160
|
|
|
|
|
|
|
B => sub { |
161
|
16
|
100
|
|
16
|
|
429
|
$_[7] ? $_[7] - $_[2]->header_size - $_[2]->start_line_size : '0' |
162
|
|
|
|
|
|
|
}, |
163
|
7
|
|
|
7
|
|
125
|
D => sub { int($_[5] * 1000000) }, |
164
|
|
|
|
|
|
|
h => $remoteaddr_cb, |
165
|
7
|
|
|
7
|
|
96
|
H => sub { 'HTTP/' . $_[1]->version }, |
166
|
19
|
|
|
19
|
|
198
|
I => sub { $_[6] }, |
167
|
|
|
|
|
|
|
l => '-', |
168
|
7
|
|
|
7
|
|
60
|
m => sub { $_[1]->method }, |
169
|
19
|
|
|
19
|
|
692
|
O => sub { $_[7] }, |
170
|
7
|
|
|
7
|
|
50
|
p => sub { $_[0]->local_port }, |
171
|
7
|
|
|
7
|
|
63
|
P => sub { $$ }, |
172
|
|
|
|
|
|
|
q => sub { |
173
|
7
|
100
|
|
7
|
|
24
|
my $s = $_[3]->query->to_string or return ''; |
174
|
2
|
|
|
|
|
184
|
return '?' . $s; |
175
|
|
|
|
|
|
|
}, |
176
|
51
|
|
|
51
|
|
436
|
r => sub { substr($_[1]->build_start_line, 0, -2) }, |
177
|
66
|
|
100
|
66
|
|
2555
|
s => sub { $_[2]->code // '-' }, |
178
|
|
|
|
|
|
|
t => sub { |
179
|
51
|
|
|
51
|
|
2077
|
$strftime->('[%d/%b/%Y:%H:%M:%S %z]', localtime($_[4][0])) |
180
|
|
|
|
|
|
|
}, |
181
|
7
|
|
|
7
|
|
28
|
T => sub { int $_[5] }, |
182
|
|
|
|
|
|
|
u => sub { |
183
|
51
|
|
|
51
|
|
1041
|
my $env = $_[1]->env; |
184
|
51
|
50
|
50
|
|
|
623
|
my $user = |
|
|
100
|
100
|
|
|
|
|
185
|
|
|
|
|
|
|
exists($env->{REMOTE_USER}) ? |
186
|
|
|
|
|
|
|
length($env->{REMOTE_USER} // '') ? |
187
|
|
|
|
|
|
|
$env->{REMOTE_USER} : '-' : |
188
|
|
|
|
|
|
|
(split ':', $_[3]->base->userinfo || '-:')[0]; |
189
|
|
|
|
|
|
|
|
190
|
51
|
|
|
|
|
660
|
return _safe($user, $safe_re) |
191
|
|
|
|
|
|
|
}, |
192
|
7
|
|
|
7
|
|
24
|
U => sub { $_[3]->path }, |
193
|
9
|
|
|
|
|
276
|
v => $servername_cb, |
194
|
|
|
|
|
|
|
V => $servername_cb, |
195
|
|
|
|
|
|
|
); |
196
|
|
|
|
|
|
|
|
197
|
9
|
50
|
|
|
|
31
|
if ($conf->{hostname_lookups}) { |
198
|
|
|
|
|
|
|
$char_handler{h} = sub { |
199
|
0
|
0
|
|
0
|
|
0
|
my $ip = $_[0]->remote_address or return '-'; |
200
|
0
|
|
|
|
|
0
|
return gethostbyaddr(inet_aton($ip), AF_INET); |
201
|
0
|
|
|
|
|
0
|
}; |
202
|
|
|
|
|
|
|
} |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
my $char_handler = sub { |
205
|
70
|
|
|
70
|
|
74
|
my $char = shift; |
206
|
70
|
|
|
|
|
100
|
my $cb = $char_handler{$char}; |
207
|
|
|
|
|
|
|
|
208
|
70
|
50
|
|
|
|
151
|
return $char_handler{$char} if $char_handler{$char}; |
209
|
|
|
|
|
|
|
|
210
|
0
|
|
|
|
|
0
|
$app->log->error("\%$char not supported."); |
211
|
|
|
|
|
|
|
|
212
|
0
|
|
|
|
|
0
|
return '-'; |
213
|
9
|
|
|
|
|
26
|
}; |
214
|
|
|
|
|
|
|
|
215
|
9
|
|
|
|
|
83
|
$format =~ s~ |
216
|
|
|
|
|
|
|
(?: |
217
|
|
|
|
|
|
|
\%\{(.+?)\}([a-z]) | |
218
|
|
|
|
|
|
|
\%(?:[<>])?([a-zA-Z\%]) |
219
|
|
|
|
|
|
|
) |
220
|
|
|
|
|
|
|
~ |
221
|
92
|
100
|
|
|
|
193
|
push @handler, $1 ? $block_handler->($1, $2) : $char_handler->($3); |
222
|
92
|
|
|
|
|
223
|
'%s'; |
223
|
|
|
|
|
|
|
~egx; |
224
|
|
|
|
|
|
|
|
225
|
9
|
|
|
|
|
41
|
chomp $format; |
226
|
9
|
|
33
|
|
|
62
|
$format .= $conf->{lf} // $/ // "\n"; |
|
|
|
50
|
|
|
|
|
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
$app->hook(after_build_tx => sub { |
229
|
66
|
|
|
66
|
|
415594
|
my $tx = $_[0]; |
230
|
66
|
|
|
|
|
144
|
my $bcr = my $bcw = 0; |
231
|
66
|
|
|
|
|
100
|
my ($r, $s, $t, $w); |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
$tx->on(connection => sub { |
234
|
66
|
|
|
|
|
2024
|
my ($tx, $connection) = @_; |
235
|
|
|
|
|
|
|
|
236
|
66
|
|
|
|
|
296
|
$t = [gettimeofday]; |
237
|
66
|
|
|
|
|
291
|
$s = Mojo::IOLoop->stream($connection); |
238
|
66
|
|
|
|
|
1013
|
$r = $s->on(read => sub { $bcr += length $_[1] }); |
|
523
|
|
|
|
|
10958552
|
|
239
|
66
|
|
|
|
|
569
|
$w = $s->on(write => sub { $bcw += length $_[1] }); |
|
81
|
|
|
|
|
2041363
|
|
240
|
66
|
|
|
|
|
612
|
}); |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
# watch for the right moment to fetch the un-expanded start-line |
243
|
66
|
|
|
|
|
503
|
$tx->req->once(progress => sub { $_[0]->build_start_line }); |
|
66
|
|
|
|
|
54102
|
|
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
$tx->on(finish => sub { |
246
|
66
|
|
|
|
|
507954
|
my $tx = shift; |
247
|
66
|
|
|
|
|
212
|
my $dt = tv_interval($t); |
248
|
|
|
|
|
|
|
|
249
|
66
|
|
|
|
|
825
|
$s->unsubscribe(read => $r); |
250
|
66
|
|
|
|
|
798
|
$s->unsubscribe(write => $w); |
251
|
66
|
|
|
|
|
778
|
$logger->(_log($tx, $format, \@handler, $t, $dt, $bcr, $bcw)); |
252
|
66
|
|
|
|
|
2295
|
}); |
253
|
9
|
|
|
|
|
85
|
}); |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
sub _log { |
257
|
66
|
|
|
66
|
|
183
|
my ($tx, $format, $handler) = (shift, shift, shift); |
258
|
66
|
|
|
|
|
207
|
my $req = $tx->req; |
259
|
66
|
|
|
|
|
405
|
my @args = ($tx, $req, $tx->res, $req->url, @_); |
260
|
|
|
|
|
|
|
|
261
|
66
|
100
|
50
|
|
|
907
|
sprintf $format, map(ref() ? ($_->(@args))[0] // '' : $_, @$handler); |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
sub _safe { |
265
|
155
|
|
|
155
|
|
1725
|
my $string = shift; |
266
|
155
|
|
66
|
|
|
774
|
my $re = shift // qr/([^[:print:]])/; |
267
|
|
|
|
|
|
|
|
268
|
155
|
50
|
|
|
|
1079
|
$string =~ s/$re/'\x' . unpack('H*', $1)/eg |
|
16
|
|
|
|
|
139
|
|
269
|
|
|
|
|
|
|
if defined $string; |
270
|
|
|
|
|
|
|
|
271
|
155
|
|
|
|
|
1007
|
return $string; |
272
|
|
|
|
|
|
|
} |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
1; |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
__END__ |