ePrometeusCorsoLinuxLinux
testi articoli
Testi Articoli  Download
Home | Introduzione | Panoramica | Form | Analisi Log | 
CorsoJava è ora Video! Free for all!
Clicca Qui!
Tutorial Perl
Analisi dei log di accesso in Perl
Utilizzo di Analog
Le espressioni regolari
Struttura del programma
Lettura dei parametri
Analisi dei log
Conclusioni


<<< Analisi dei log >>>

Tutto è pronto per il nocciolo del nostro lavoro, che grazie alla preliminare costruzione delle espressioni regolari si rivela particolarmente semplice.


# ritorna un array con i parametri di input per Main
# che possono essere o la linea di comando o la QUERY_STRING
# imposta la variabile globale IsCgi a 1 
# se si tratta di una cgi, 0 altrimenti
sub GetParam {
  $IsCgi = 0;
  return @ARGV if(@ARGV) ;
  $IsCgi = exists $ENV{'QUERY_STRING'};
  if($IsCgi) {
    $_=$ENV{'QUERY_STRING'};
    foreach(split(/&/)) {
      s/\+/ /g;
      s/%([0-9a-fA-F][0-9a-fA-F])/sprintf("%c",hex($1))/eg;
      unshift(@ARGV, $_);
    }
  }
  return @ARGV;
}

# analisi dei parametri in input
# input:  @_
# output: $LogFile, $Begin, $End, $Url, $NUrl, $Site , $NSite
sub ParseParam {
  local $t, $u;
  local %msg= 
    ('url=', 'Includi URL', 'url=!', 'Escludi URL',
     'site=', 'Includi Sito', 'site=!', 'Escludi sito');
  $param_limit = '';
  while($_=shift) {
    $LogFile=$1 if !$IsCgi && /^file=(.+)/;
    ($Begin=date2int($param_begin=$1) 
     || &PrintexError("Data di inizio errata"))
      if(/^begin=(.+)/);
    ($End=date2int($param_end=$1) 
     || &PrintexError("Data di fine errata"))
      if(/^end=(.+)/);
    if(/^(url=!?|site=!?)(.+)/) {
      $param_limit .= "$msg{$1}: $2<br>\n";
      # converti i parametri in espressione regolare
      $t = $1; $_=$2;
      s/\W/\\$1/g;           s/\\\?/\./g;
      s/\\\*/\.\*/g;
      $u=$_; $_=$t;
      $Url   .= "$u|" if /^url=$/;
      $NUrl  .= "$u|" if /^url=!$/;
      $Site  .= "$u|" if /^site=$/;
      $NSite .= "$u|" if /^nsite=!$/;
    }
  }
  $Url =~ s/\|$//;
  $NUrl =~ s/\|$//;
  $Site =~ s/\|$//;
  $NSite =~ s/\|$//;
}

# abbreviazione dei mesi dell'anno, in italiano e in inglese

%MONTHS = 
  ('jan', 1, 'feb', 2, 'mar', 3, 'apr', 4, 'may', 5, 'jun', 6,
   'jul', 7, 'aug', 8, 'sep', 9, 'oct', 10, 'nov', 11, 'dec', 12,
   'gen', 1, 'feb', 2, 'mar', 3, 'apr', 4, 'mag', 5, 'giu', 6,
   'lug', 7, 'ago', 8, 'set', 9, 'ott', 10, 'nov', 11, 'dic', 12);

# date2int <date>
# converte una data come 9/Lug/1968 in un numero come 19680709
# (facile da comparare); ritorna 0 se la data e' errata
sub date2int {
  $_=shift; tr/A-Z/a-z/;
  return $3 * 10000 + $MONTHS{$2}*100 + $1
    if /^(\d\d?)\/(\w\w\w)\/(\d\d\d\d)$/ && $MONTHS{$2};
  return 0;
}

Si apre il file e si legge riga per riga, separando i campi con una split e selezionando quelli che ci interessano con un cosiddetto array slice:


       ($site, $_, $url, $status, $tot) = (split) [0,3,6,8,9]


In Perl è possibile utilizzare le parentesi quadre non solo per indicizzare un array ma anche per selezionare un sottoinsieme dell’array (appunto uno slice).

Nell’analisi dell’input ci sono due finezze: innanzitutto occorre considerare soltanto le richieste che non hanno dato errore ($status == 200): tipicamente i log sono pieni di richieste andate male (richieste di URL sbagliati per esempio), che sebbene relativamente numerose sono del tutto irrilevanti dal punto di vista statistico e “sporcano” il risultato con una miriade di casi strani. Un altro aggiustamento da fare è togliere dalle chiamate di CGI la parte che specifica i parametri della CGI (che altrimenti apparirebbero tutte come URL distinte mentre in realtà si riferiscono ad un insieme ristretto di URL). Le URL che invocano una CGI hanno un formato simile a questo:


	/cgi-bin/try?a=1&b=2 


Per “pulire” le URL togliamo tutto quanto segue un ? alla fine della stringa. Il resto del programma semplicemente confronta le date ed esegue il matching per i vari casi, scartando le righe che non soddisfano le condizioni. Quando una riga soddisfa tutte le restrizioni, grazie ad un array associativo si conteggiano contatti e byte per ogni URL e per ogni sito:


	++$StatUrlCount{$url}; 
	$StatUrlTot{$url} += $tot;
	++$StatReqCount{$site}; 
	$StatReqTot{$site} += $tot;

Nella procedura di stampa dei risultati vengono semplicemente analizzati e stampati i risultati contenuti negli array associativi risultanti.


# stampa i risultati ed esci
sub PrintexResults {
  PrintBegin("AnaLog $VERSION");
  print "<h2>&#166 1996 Michele Sciabarr&agrave;</h2>";
  print "<h2>Inizio: $param_begin</h2>\n" if($param_begin) ;
  print "<h2>Fine: $param_end</h2>\n" if($param_end) ;
  print "<h2>\n$param_limit</h2>\n";

  print "<h1>Statistica Richieste</h1>\n<h2></h2>";
  print "<table>\n";
  print "<tr><th>URL <th>Conteggio <th>Totale byte <br>\n";
  foreach(sort { -( $StatUrlCount{$a} <=> $StatUrlCount{$b} ) 
          || $a cmp $b } 
	  keys %StatUrlCount) {
    print "<tr><td>$_ <td>$StatUrlCount{$_} ".
      "<td><font size=-1>$StatUrlTot{$_}</font><br>\n";
  }
  print "</table>\n";
  print "<h1>Statistica Contatti</h1>\n";
  print "<table>\n";
  print "<tr><th>Sito <th>Conteggio <th>Totale byte <br>\n";
  foreach(sort { - ($StatReqCount{$a} <=> $StatReqCount{$b}) 
          || $a cmp $b } keys %StatReqCount) {
    print "<tr><td>$_ <td>$StatReqCount{$_} ".
      "<td><font size=-1>$StatReqTot{$_} </font><br>\n";
  }
  print "</table>\n";
  PrintexEnd;
}

La procedura sarebbe banale se non fosse per l’uso “avanzato” della primitiva sort utilizzato per stampare il risultato in maniera ordinata:


         foreach(sort { -( $StatUrlCount{$a} <=> $StatUrlCount{$b} ) 
            || $a cmp $b }  keys %StatUrlCount) {
		print  "<tr><td>$_ <td>$StatUrlCount{$_} ".
			"<td><font size=-1>$StatUrlTot{$_}</font><br>\n";
	}


La keys %StatUrlCount fornisce tutti gli indici dell’array associativo (le “chiavi”). Sfortunatamente l’ordine è pressoché casuale e occorre bisogna ordinarlo per presentare il risultato in un formato facilmente leggibile. Ma in base a quali criteri va fatto l’ordinamento? Il criterio scelto è il seguente: vogliamo avere per prime le URL che hanno contato il maggior numero di contatti, ma a pari numero di contatti vogliamo il risultato ordinato alfabeticamente.

Il Perl ha una primitiva di sort applicabile ad un array che consente di specificare con un blocco di codice la funzione di comparazione di due elementi. Tipicamente tutti gli algoritmi di sort possono essere parametrizzati in base al metodo di accesso agli elementi (il cosiddetto selettore) e a una funzione di comparazione tra elementi. In Perl poiché gli elementi sono contenuti in un array non abbiamo problemi di specificare il selettore: si indicizza l’array. Per specificare funzione di comparazione bisogna fornire un blocco che compari due elementi e ritorni -1, 0 o 1 a seconda che il primo elemento sia minore, uguale o maggiore del secondo.

Il blocco viene valutato per comparare ogni coppia di elementi che l’algoritmo necessita di confrontare per ordinare l’array. Per ragioni di efficienza non si tratta di una subroutine e non viene fatto un passaggio di parametri: gli elementi da comparare vengono posti nelle variabili $a e $b. Il default è il blocco { $a cmp $b }, che compara due stringhe. Noi abbiamo adottato:


	{ - ($StatReqCount{$a} <=> $StatReqCount{$b}) || $a cmp $b } 


In questo modo si ordina innanzitutto per numero di accessi come sono stati conteggiati nello $StatReqCount e in caso di eguaglianza di numero di accessi ( se <=> ritorna 0, che è falso) si effettua la comparazione alfabetica tra le chiavi. Notare il meno, che serve ad invertire il risultato del <=> e ordinare in senso decrescente il numero di accessi.

ePrometeus s.r.l. - Web Software House & Open Source System Integrator
MILANO - SAN BENEDETTO DEL TRONTO(AP)
Contatti: info@eprometeus.com