#!/usr/bin/perl # config my $dir = "/var/log/ispconfid/httpd/"; my $files = "*/*access.log"; my $site = "(.*)/access.log"; my $statefile = "/tmp/logstate"; `touch $statefile` unless (-f $statefile); local $type="combined"; local $nsec=7; local $debug=1; my $scan_interval=5; # minutes # perl modules use File::Tail::Multi; use Storable qw(freeze thaw); use List::Util qw(min max); use IPC::ShareLite ':lock'; require Data::Dumper if $debug; use Munin::Plugin; # shared mem local $share = IPC::ShareLite->new( -key => 'mapl', -create => 1, -destroy => 1, -exclusive => 0, -mode => '0666' ) or die $!; # drop stored data on reload $share->store( freeze {} ); # tail log files my $tail=File::Tail::Multi->new ( Files=>["$dir/$files"], ScanForFiles=>$scan_interval, Debug=>0, LastRun_File => $statefile, RemoveDuplicate=>0, NumLines=>0, OutputPrefix=>"f" ); # read to current position $tail->read; # register counting function $tail->Function(\&count); local $temp; my ($file,$ip,$logname,$user,$rtime,$method,$request,$protocol,$status,$bytes,$referer,$useragent,$time); sub count { foreach $_ (@{shift()}) { if ((()=/"/g)==2) { # common with filename prefix, optionally add time and vhost at the end ($file,$ip,$logname,$user,$rtime,$method,$request,$protocol,$status,$bytes,$time,$vhost)=/^(.*?)\s:\s(.*?)\s(.*?)\s(.*?)\s\[(.*?)\]\s"(.*)\s(.*?)\s(.*?)"\s(\d*)\s(\S*)\s?(\S*)\s?(\S*?)$/o; } elsif ((()=/"/g)==6) { # combined with filename prefix, optionally add time and vhost at the end ($file,$ip,$logname,$user,$rtime,$method,$request,$protocol,$status,$bytes,$referer,$useragent,$time,$vhost)=/^(.*?)\s:\s(.*?)\s(.*?)\s(.*?)\s\[(.*?)\]\s"(.*)\s(.*?)\s(.*?)"\s(\d*?)\s(.*?)\s"(.*?)"\s"(.*?)"\s?(\S*)\s?(\S*)$/o; }; #find sitename $file=~s/$site/$1/; $file=$vhost if $vhost; # skip broken lines next unless $file; # sitename to munin fieldname my $vpm=clean_fieldname("$file"); $temp{$vpm}{'label'}="$file"; $temp{$vpm}{'label'}=~s/www\.//; # count all requests $temp{$vpm}{'requests'}++; if ($bytes) { $bytes=~s/-/0/; # bytes transmitted $temp{$vpm}{'bytes'}+=$bytes; # max bytes $temp{$vpm}{'max_bytes'}=max($temp{$vpm}{'max_bytes'},$bytes) || 0; # average bytes $temp{$vpm}{'avg_bytes'}=$temp{$vpm}{'bytes'}/$temp{$vpm}{'requests'} || 0; } # count by status / error code $temp{$vpm}{"status"}{$status}++ if $status; if ($time) { # microsec to millisec $time=sprintf("%d",$time/1000); # min/max execution time $temp{$vpm}{'max_time'}=max($temp{$vpm}{'max_time'},$time) || 0; # cumulative execution time $temp{$vpm}{'time'}+=$time; # average time $temp{$vpm}{'avg_time'}=$temp{$vpm}{'time'}/$temp{$vpm}{'requests'} || 0; } }; }; while (1) { # tail files, calls &count with linearray $tail->read; # begin transaction $share->lock(LOCK_EX); # get data (may be updated by other loggers too) my %old=eval{%{thaw($share->fetch)}}; # using eval to suppress thaw error on empty string at the first run foreach my $vpm (keys %temp){ # merge values $old{$vpm}{'label'}=$temp{$vpm}{'label'}; $old{$vpm}{'bytes'}+=$temp{$vpm}{'bytes'} if $temp{$vpm}{'bytes'}; $old{$vpm}{'requests'}+=$temp{$vpm}{'requests'} if $temp{$vpm}{'requests'}; $old{$vpm}{'time'}+=$temp{$vpm}{'time'} if $temp{$vpm}{'time'}; # avoid div by zero my $div=($old{$vpm}{'requests'} <1)?1:$old{$vpm}{'requests'}; # recalc average on merged data for multiple datasources, use local average after purge/restart $old{$vpm}{'avg_time'}=($old{$vpm}{'avg_time'}>0)?sprintf("%d",($old{$vpm}{'time'}+$temp{$vpm}{'time'})/$div):sprintf("%d",$temp{$vpm}{'avg_time'}); $old{$vpm}{'avg_bytes'}=($old{$vpm}{'avg_bytes'}>0)?sprintf("%d",($old{$vpm}{'bytes'}+$temp{$vpm}{'bytes'})/$div):sprintf("%d",$temp{$vpm}{'avg_bytes'}); $old{$vpm}{'max_time'}=max($old{$vpm}{'max_time'},$temp{$vpm}{'max_time'}) || 0; $old{$vpm}{'max_bytes'}=max($old{$vpm}{'max_bytes'},$temp{$vpm}{'max_bytes'}) || 0; # reset local counters foreach my $check qw(requests bytes time max_bytes avg_bytes max_time avg_time) { $temp{$vpm}{$check}=0; } # reset status counts foreach my $val (keys %{$temp{$vpm}{'status'}}) { $old{$vpm}{'status'}{$val}+=$temp{$vpm}{'status'}{$val}; $temp{$vpm}{'status'}{$val}=0; } }; # save to shm print Data::Dumper::Dumper(%old) if $debug; $share->store( freeze \%old ); # end transaction $share->unlock; # parse/write every n seconds (plus processing time) sleep $nsec; }