package LogAnalyze; use strict; use Carp; use Net::FTP; use Cwd; use Fcntl qw(O_WRONLY O_RDONLY O_TRUNC O_CREAT LOCK_SH LOCK_EX); use Data::Dumper; use URI::Escape; our $Version = '0.0.2'; our $ErrorMsg; # private functions my $getDate = sub { my @date = (localtime)[5,4,3]; $date[0] += 1900; $date[1]++; return sprintf("%04d-%02d-%02d", @date); }; my $getFTPFile = sub { my $loc = shift; my $opt = shift; my $ftp = Net::FTP->new($opt->{ftp}, Debug => 0) or return $@; $ftp->login($opt->{user}, $opt->{pass}) or return $ftp->message; $ftp->cwd($opt->{ftp_dir}) or return $ftp->message; $ftp->get($opt->{logfile}, $loc) or return $ftp->message; $ftp->quit; return 0; }; my $parseLine = sub { my $l = shift; chomp $l; my $sp = '\s+'; # space my $ip = '(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+'; # IP my $id = '(\S+)\s+'; # misc. my $sc = '(\d{1,}|-)\s+'; # HTTP code, length my $dt = '(\[.+?\])\s+'; # date, time my $rq = '"([A-Z]+?)\s(.+?)\s(.+?)"\s+'; # method, req, proto my $ua = '"([^"]+)"'; # quoted string my $re = $ip.$id.$id.$dt.$rq.$sc.$sc.$id.$ua.$sp.$ua; my @a = ($l =~ /^$re/); return \@a; }; # constructor sub new { my $class = shift; my $pwd = shift; my $local = shift; mkdir $pwd unless -d $pwd; my $ref = { pwd => $pwd || cwd(), cwd => cwd(), data => [] }; $ref->{localfile} = $local || 'log_'.&$getDate.'.txt'; my $obj = bless $ref, $class; return $obj; } # public functions sub getLogFile { my %options; @options{'ftp', 'user', 'pass', 'ftp_dir', 'logfile'} = (); my $obj = shift; my $opt = shift; foreach my $k (keys %$opt) { unless( exists $options{$k} ) { $ErrorMsg = "Option $k does not exist"; return 0; } } chdir $obj->{pwd}; return 1 if -e $obj->{localfile}; $ErrorMsg = &$getFTPFile($obj->{localfile}, $opt); return $ErrorMsg ? 0 : 1; } sub parseLogFile { my $obj = shift; { open LOG, $obj->{localfile} or last; flock LOG, LOCK_SH; while() { my $erg = &$parseLine($_); push @{$obj->{data}}, { ip => $erg->[0], date => $erg->[3], meth => $erg->[4], url => $erg->[5], proto => $erg->[6], code => $erg->[7], length => $erg->[8], host => $erg->[9], ref => $erg->[10], uas => $erg->[11] }; } } close LOG; $ErrorMsg = $!; return ($ErrorMsg ? 0 : 1); } sub statTop { my $obj = shift; my $opt = shift; my $field = $opt->{field} || croak 'LogAnalyze->statTop: No field selected for statistics'; my $level = $opt->{level} || 10; my $temp = {}; my $stat = []; for( @{$obj->{data}} ) { $temp->{$_->{$field}}++ if $_->{$field}; } my @keys = sort { $temp->{$b} <=> $temp->{$a} } keys %$temp; for(0..$level) { push @$stat, { $field => $keys[$_], count => $temp->{$keys[$_]} } if $keys[$_]; } return $stat; } sub statSE { my $obj = shift; my $stat = {}; for( @{$obj->{data}} ) { if( $_->{ref} ) { if( $_->{ref} =~ /(google|t\-online|msn|yahoo)/ ) { my $se = $1; $stat->{$se} = [] if !$stat->{$se}; $_->{ref} =~ /q=(.+?)(&|$)/; push @{$stat->{$se}}, uri_unescape($1); } } } return $stat; } sub dailyTraffic { my $obj = shift; my $stat = {}; for( @{$obj->{data}} ) { $_->{date} =~ /(\d{2}\/[a-z]{3}\/\d{4})/i; $stat->{$1} += $_->{length} if( $_->{length} ne '-' ); } return $stat; } sub DESTROY { my $obj = shift; chdir $obj->{cwd}; } 1;