Ausreisekontrolle
Die Benutzer einer Website registriert die Log-Datei des Servers, die populäre Analyseprogramme detailliert und farbenfroh darzustellen wissen. Doch wieviel Abenteuerlustige wandern über die eingearbeiteten Links zu externen Seiten? Diese Zahlen erfaßt nur der Click-Through-Tracker.
- Michael Schilli
Ob nun ein Werbebanner oder ein einfacher Link auf eine andere Web-Seite verweist: Jeder Webmaster möchte gerne wissen, wer welche Knöpfchen auf seiner Website drückte, um fremde Seiten zu erobern - und sei es nur, daß die Anzeigenabteilung mit den Erfolgszahlen ihrer Werbebanner angeben kann. Ein ordinärer Link, als HTML-Code
<A HREF="http://remote.host.com"> Klick mich! </A>
oder als Bestandteil der Sequenzdatei des in der letzten iX vorgestellten Bannerservers [1], zeigt auf http://remote.host.com. Klickt der Anwender darauf, springt der Browser dorthin, ohne der bisherigen Seite adieu zu sagen - und demnach weiß dort niemand, daß dieser Vorgang überhaupt stattgefunden hat.
Ein für den Web-Surfer unsichtbarer Zwischenschritt bringt die Lösung: Der Link zeigt nicht auf die anzuspringende Seite, sondern auf ein CGI-Skript der heimischen Website, das den Vorgang protokolliert und anschließend dem Browser mittels einer Redirect-Anweisung vorgaukelt, das Dokument habe seinen Standort gewechselt. Daraufhin verzweigt jener anstandslos zur eigentlichen Seite:
<A HREF="http://mysite.com/cgi/go.cgi/http://remote.host.com"> Klick mich! </A>
ruft das Skript go.cgi im cgi-Verzeichnis der darstellenden Website mysite.com auf und gibt ihm als PATH_INFO-Parameter die Zeichenkette ‘http://remote.host.com’ mit. Er veranlaßt es, den Browser des Anwenders auf http://remote.host.com weiterzulotsen und anschließend in aller Ruhe den Vorgang in eine Log-Datei zu protokollieren.
Einheitliches Format für Log-Dateien
Diese Datei könnte man auf alle möglichen Arten füllen, doch da die gesammelten Daten nicht dort vermodern, sondern mit frei verfügbarer Software später ansprechend dargestellt werden sollen, benutzt das CGI-Skript das erweiterte ‘Common Log File Format’. Daran halten sich die meisten Server beim Schreiben von Log-Dateien.
Klickt etwa ein Benutzer des Rechners ppp.isp.com auf der Web-Seite http://referer.com/index.html den Link http://www.remote.com/index.html an, schreibt www.remote.com folgende Zeile in seine Log-Datei:
ppp.isp.com - - [14/Oct/1998:20:32:04 -0700] \
"GET /index.html HTTP/1.0" \
200 1234 http://www.referer.com/index.html \
"Mozilla/3.04Gold (X11; I; Linux 2.0.30 i586)"
Die insgesamt neun Felder des Eintrags haben folgende Bedeutung:
- Name oder IP-Adresse des anfragenden Rechners. Ist der Benutzer über PPP beim Service-Provider angemeldet, steht hier gerne mal ein ‘dynamischer’ Name, der die dynamisch zugewiesene IP-Adresse widerspiegelt.
- Ein Bindestrich.
- Die UserID, falls es sich um eine paßwortgeschützte Seite handelt, falls nicht, ein weiterer Bindestrich
- In eckigen Klammern das aktuelle Datum des Zugriffs mit Zeitzonenangabe relativ zu GMT.
- In einem in Anführungszeichen eingeschlossenen String: Zugriffsmethode (meist GET oder POST), angeforderte Datei, Protokollversion.
- Statuscode (200 für OK, 404 für Document not found, et cetera).
- Anzahl der übertragenen Bytes (1234).
- URL der referenzierenden Seite, falls vorhanden (erweitertes Common Log File Format).
- Art und Version des Browsers, Betriebssystem mit Version (erweitertes Common Log File Format).
Gibt der im Listing vorgestellte Click-Through-Tracker seine Log-Daten auch in diesem Format aus, interpretieren bekannte Analyseprogramme wie webalizer oder analog die Daten, als kämen sie aus der Datei eines Web-Servers. Die Abbildungen 1 bis 3 zeigen eine Auswahl dessen, was der webalizer (Installation siehe ‘Webalizer-Installation’) aus dem Datensalat zaubert: Säulengrafiken, die die stündliche und tägliche Verteilung eingehender Benutzerklicks darstellen, die Top-30-Liste angeforderter URLs, eine Liste der beliebtesten Browser und vieles mehr.
Webalizer-Installation
Der Webalizer, ein Analyseprogramm für Log-Dateien von Web-Servern, steht unter http://www.mrunix.net/webalizer/download.html in der Version 1.20 zur kostenlosen Abholung bereit. Für die gängigen Unix-Varianten gibt es Binärversionen, die man nur in einem neuen Verzeichnis entpacken muß. Neben dem Programm webalizer steht dort die Konfigurationsdatei sample.conf, die nach webalizer.conf kopiert werden muß. Folgende Einträge sind anzupassen:
LogFile Pfad/zur/Log-Datei
OutputDir Pfad/zum/Ausgabe/Verzeichnis
webalizer liest die zu analysierende Log-Datei ein, verarbeitet ihre Datensätze und legt das Ergebnis monatsweise in mehreren Dateien (*.html und *.gif) im definierten Ausgabeverzeichnis ab. index.html in diesem Verzeichnis bietet einen Einstiegspunkt, um durch verschiedene Monatsdaten zu stöbern.
Soll der Webalizer statt der voreingestellten monatlichen Zusammenfassungen die Daten täglich aktualisieren, ist folgendes zu beachten: Damit er nicht immer wieder die gesamten Daten seit Monatsanfang durchorgeln muß, bietet er inkrementelle Verarbeitung. Dabei ‘merkt’ sich das Programm die bereits ausgewerteten Log-Daten in einer sogenannten Cache-Datei, so daß es nur noch mit neuen Daten gefüttert werden muß. Um dies zu aktivieren, ist
Incremental yes
in webalizer.conf einzustellen, und schon kommt er mit täglich eingespeisten rotierten Log-Dateien zurecht.
LISTING
1 #!/usr/bin/perl -w
2 ##################################################
3 # Click-Through Tracker
4 #
5 # 1998, schilli@perlmeister.com
6 ##################################################
7
8 use Socket; # For address reversal
9
10 my $LOG_FILE = "/services/http/DATA/go.log";
11
12 my $path = $ENV{PATH_INFO};
13 $path =~ s#^/##g;
14
15 die "Redirect Path not set" unless defined $path;
16
17 print "Location: $path\r\n\r\n"; # Issue redirect
18
19 my $newpid = fork(); # New Child process
20
21 # Parent exits on sucessful creation of child
22 exit(0) if (defined $newpid && $newpid != 0);
23 # child running from here
24 close(STDOUT); # Weºre done sending
25 log_request($path); # Log redirect
26
27 ##################################################
28 sub log_request {
29 ##################################################
30 my $url = shift;
31
32 open(LOG, ">>$LOG_FILE") ||
33 die("Cannot open $LOG_FILE: $!");
34
35 flock(LOG,2); # Lock exclusive
36 seek(LOG, 0, 2); # Seek to end
37
38 my @months = qw(Jan Feb Mar Apr May Jun Jul
39 Aug Sep Oct Nov Dec);
40
41 my ($sec,$min,$hour,$mday,$mon,$year) =
42 localtime(time);
43
44 $time = sprintf(
45 "[%02d/%s/%d:%02d:%02d:%02d +0000]",
46 $mday, $months[$mon], $year + 1900,
47 $hour, $min, $sec);
48
49 print LOG reverse_lookup($ENV{REMOTE_ADDR}),
50 " -", # Always a dash
51 " -", # User name
52 " $time", # Time
53 " \"GET $url HTTP/1.0\"", # Request
54 " 200", # Status code
55 " 0", # Bytes: 0
56 " $ENV{HTTP_REFERER}", # Referer
57 # User Agent
58 " \"$ENV{HTTP_USER_AGENT}\"",
59 "\n";
60 close LOG;
61 }
62
63 ##################################################
64 sub reverse_lookup {
65 ##################################################
66 my $ip = shift;
67
68 # Return if itºs already a hostname
69 return $ip if($ip =~ /[a-z]/);
70
71 # IP -> 4 byte struct
72 my $ipstruct = inet_aton($ip);
73
74 # Return resolved host or original IP
75 gethostbyaddr($ipstruct, AF_INET) || $ip;
76 }
Listing go.cgi zeigt die Implementierung des protokollierenden CGI-Skripts. Zeile 10 legt die Datei fest, in die go.cgi seine Protokolldaten schreibt. Sie muß für den Benutzer, unter dessen Namen der Web-Server läuft (meist nobody) beschreibbar sein.
PATH_INFO als Umleitungsschild
Ruft man ein CGI-Skript http://yada.yada.com/cgi-bin/script als http://yada.yada.com/cgi-bin/script/some/path auf, liegt ihm nach dem Aufruf der Pfad /some/path in der Umgebungsvariablen PATH_INFO vor - so will es das CGI-Protokoll. Zeile 12 in go.cgi holt den Parameter, Zeile 13 schneidet das führende / ab und Zeile 15 beendet das Skript, falls kein Pfad vorhanden ist.
Läuft alles wie vorgesehen, leitet die Ausgabe von Zeile 17 den anfragenden Browser an die neue Adresse weiter. Solche Redirect-Anweisungen verarbeitet letzterer, ohne daß der Anwender davon Wind bekommt (nur bei genauem Hinsehen bemerkt man, daß sich der angezeigte URL kurzfristig verändert). Da das Schreiben in die Log-Datei einige Zeit in Anspruch nehmen kann, die Kommunikation mit dem Browser zu diesem Zeitpunkt aber bereits erledigt ist, erzeugt go.cgi nun mit fork() einen Child-Prozeß, der das Log-Schreiben übernimmt, und beendet sich anschließend. close(STDOUT) sorgt dafür, daß der steuernde Web-Server sich nicht mehr um das Skript kümmert.
Die Funktion log_request mit dem zu notierenden URL als Parameter hängt einen Eintrag im ‘Extended Common Log File Format’ an die Log-Datei an. Um gegen parallel laufende Web-Server-Prozesse gewappnet zu sein, sichert sie die Datei vor dem Schreiben mit flock(). Diese Funktion synchronisiert den Zugriff auf eine Datei, indem sie jeweils nur einem einzigen Prozeß oder Thread den Zugriff gewährt und alle anderen, die ebenfalls einen flock() darauf versuchen, so lange blockiert, bis der Eigentümer die Sperre wieder löst. Aus der aktuellen Uhrzeit entsteht ein String vom Format
14/Oct/1998:20:32:04 +0000
(ist die gewählte Zeitzone nicht GMT, ist der String in Zeile 45 entsprechend anzupassen), und die Umgebungsvariablen $HTTP_REFERER und $HTTP_USER_AGENT liefern gemäß dem CGI-Protokoll die referenzierende Seite und den Browsertyp des Anwenders. Da $REMOTE_ADDR nicht den Namen des aufrufenden Rechners beinhaltet, sondern dessen IP-Adresse, versucht die Funktion reverse_lookup mit ihr als Parameter den Rechnernamen zu bestimmen und gibt diesen im Erfolgsfall zurück, andernfalls bleibt es bei der IP-Adresse.
MICHAEL SCHILLI
arbeitet als Web-Engineer für America Online Inc., San Mateo. Er ist Autor des 1998 bei Addison-Wesley erschienenen Buches ‘GoTo Perl 5’.
Literatur
[1] Michael Schilli; Rotationsprinzip, Anzeigen schalten im Web
[2] Randal Schwartz; Click-Through Tracking in Perl; WebTechniques 5/98, S. 28 ff. (ck)