package Tail::Stat::Plugin::apache; =head1 NAME Tail::Stat::Plugin::apache - Statistics collector for Apache web-server =cut use strict; use warnings qw(all); =head1 SYNOPSIS tstatd -o clf apache httpd.access_log =head1 LOG FORMATS Apache has predefined log format named B. This format defined as: LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined This format produces such lines: 1.2.3.4 - - [03/Feb/2010:00:01:03 +0300] "GET / HTTP/1.0" 200 89422 "http://www.rambler.ru/" "Opera/9.80" You can extend this formats with '%T' characters for logging time taken to serve the request: LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" rt=%T" timed Extended format creates log records like this: 1.2.3.4 - - [03/Feb/2010:00:01:03 +0300] "GET / HTTP/1.0" 200 89422 "http://www.rambler.ru/" "Opera/9.80" rt=2.929 =head1 OPTIONS =over =item C Simplify regular expression (don't accumulate information about client timing). =back =head1 STATISTICS =head2 Overall statistics =head3 HTTP traffic =over =item C Total number of served requests. =item C Total number of sent bytes. =item C Total number of malformed requests. =back =head3 HTTP methods =over =item C Total number of served HEAD requests. =item C Total number of served GET requests. =item C Total number of served subrequests. =item C Total number of served POST requests. =item C Total number of served another types of requests. =back =head3 HTTP versions =over =item C Total number of served HTTP/0.9 requests. =item C Total number of served HTTP/1.0 requests. =item C Total number of served HTTP/1.1 requests. =back =head3 HTTP statuses =over =item C Total number of served requests with status of xxx. =item C Total number of served requests with status of 100-199. =item C Total number of served requests with status of 200-299. =item C Total number of served requests with status of 300-399. =item C Total number of served requests with status of 400-499. =item C Total number of served requests with status of 500-599. =back =head2 Last statistics =head3 HTTP traffic =over =item C Total number of served requests during last window. =item C Total number of sent bytes during last window. =item C Total amount of time elapsed to serve requests during last window B available unless C option in use. =back =cut use base qw(Tail::Stat::Plugin); use List::Util qw(sum); sub regex { my $self = shift; my $re = qr{ ^ (\S+) # remote address [0] \s (\S+) # ident [1] \s (\S+) # remote user [2] \s \[([^\]]+)\] # time local [3] \s "(?: # request ([A-Z]+) # http method [4] \s+ ( # request uri [5] /\S* # abs_path | http://\S* # absoluteURI ) (?: # http version > 0.9 \s+ HTTP/(1\.[01]) # http version [6] )? | ([^"]*) # malformed request [7] )" \s ([1-5]\d{2}) # http status [8] \s (\d+|-) # content length [9] \s "([^"]*)" # referrer [10] \s "([^"]*)" # user agent [11] }x; $re .= qr{ (?: rt=([\d\.]+) # request time [12] | . )* }x unless $self->{clf}; return $re; } sub process_data { my $self = shift; my ($ref,$pub,$prv,$win) = @_; $pub->{http_request}++; $win->{http_request}++; unless ($ref->[9] eq '-') { $pub->{http_byte} += $ref->[9]; $win->{http_byte} += $ref->[9]; } if ($ref->[4]) { # method $pub->{'http_method_'.$ref->[4]}++; if ($ref->[6]) { # version $pub->{'http_version_'.$ref->[6]}++; } elsif ($ref->[4] ne 'INC') { $pub->{'http_version_0.9'}++; } } else { $pub->{malformed_request}++; } $pub->{'http_status_'.$ref->[8]}++; # extended part unless ($self->{clf}) { $win->{request_time} += $ref->[12] if $ref->[12]; } return 1; } sub process_window { my $self = shift; my ($pub,$prv,$wins) = @_; for my $x ( qw( http_request http_byte ), $self->{clf} ? () : qw( request_time ) ) { $pub->{'last_'.$x} = sum ( map { $_->{$x} || 0 } @$wins ) || 0; } } sub stats_zone { my ($self,$zone,$pub,$prv,$wins) = @_; # required keys defaults my %out = map { $_ => 0 } qw( malformed_request http_request last_http_request http_byte last_http_byte http_method_head http_method_get http_method_inc http_method_post http_method_other http_version_0_9 http_version_1_0 http_version_1_1 http_status_1xx http_status_2xx http_status_3xx http_status_4xx http_status_5xx ), $self->{clf} ? () : qw( last_request_time ); # agregate required keys for ( keys %$pub ) { # http_method /^http_method_(HEAD|GET|INC|POST)/ and do { $out{'http_method_'. lc $1} += $pub->{$_}; next; }; /^http_method_/ and do { $out{'http_method_other'} += $pub->{$_}; next; }; # http_status /^http_status_([1-5])/ and do { $out{'http_status_'. $1.'xx'} += $pub->{$_}; # particular statuses $out{$_} += $pub->{$_}; next; }; # http_version /^http_version_(\S+)/ and do { (my $v = $1) =~ s/\./_/g; $out{'http_version_'. $v} += $pub->{$_}; next; }; # extended mode unless ($self->{clf}) { # copy remainings values as is $out{$_} += $pub->{$_}; next; } # simple attributes /^(?: malformed_request | http_request | last_http_request | http_byte | last_http_byte )$/x and do { $out{$_} += $pub->{$_}; next; }; } map { $_.': '.$out{$_} } sort keys %out; } sub parse_error { 'notice' } =head1 AUTHOR Oleg A. Mamontov, C<< >> =head1 COPYRIGHT This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut 1;