Netzwerkanalyse, Fehlersuche, Testberichte
Home / News
Netzwerkanalyse
Grundlagen
Verkabelung
Erste Schritte
Baselining
Let's sniff
Tools
Auswerten/Filtern
Typische Probleme 1
Typische Probleme 2
Sicherheit
Bücher
Tutorials
Cacti
DSL einrichten
DynDNS einrichten
m0n0wall | pfSense
Nmap
VirtualBox
Wireshark
FAQ
Know How
Testberichte
Hardware-DB
Netzwerklexikon
Links / Service
Suche
Kontakt
Impressum
Feedback
Sitemap
Tutorials

Zugriffsstatistiken und Counter auf Webseiten

Sie wollen sehen, wer Ihre Webseiten besucht? Auf dem eigenen Server ist das kein Problem, alle Webserver liefern detaillierte Logfiles. Auf gemietetem Webspace bekommt man allerding nicht immer seine Logfiles zu sehen.

Mit Hilfe eines kleine CGI-Scriptes kann man selber Counter realisieren.

Daten speichern

Zuerst brauchen wir eine Ablage für die gesammelten Daten. Empfehlenswert ist eine MySQL-Tabelle mit der folgenden Struktur:

#
# Tabellenstruktur für Tabelle `log`
#

CREATE TABLE `log` (
  `id` bigint(20) NOT NULL auto_increment,
  `timestamp` timestamp(14) NOT NULL,
  `url` text NOT NULL,
  `remote_addr` text,
  `remote_host` text NOT NULL,
  `user_agent` text NOT NULL,
  `referer` text NOT NULL,
  PRIMARY KEY  (`id`)
) TYPE=MyISAM COMMENT='Log fuer Webserver';

Alternativ ist auch die Ablage in einer Textdatei realisierbar.

Integration in die Webseiten

Es gibt mehrere Möglichkeiten den Zähler in eine HTML-Seite zu integrieren: per SSI (Server Side Include) oder als Grafik.
SSI hat den großen Vorteil, das wirklich jeder Abruf der Seite erfasst wird. Ein als Grafik eingebundener Zähler wird z.B. von Suchmaschinen oder Browsern mit abgeschalteter Grafikanzeige nicht ausgelöst.

Server Side Includes SSI

Server Side Includes werden vom Webserver vor der Auslieferung der Seite an den Client abgearbeitet. In einer HTML-Datei sieht das dann so aus:

<HTML>
<HEAD>
<TITLE>Testseite</TITLE>
</HEAD>
<BODY>
<P>
Text Text Text

<!--#include virtual="/cgi-bin/ssicounter.pl" -->

</BODY>
</HTML>

Hier wird am Ende der Datei das Script ssicounter.pl eingefügt. Die Ausgabe des Scriptes erscheint dann im Browser.

Das Script ssicounter.pl sieht so aus:

#!/usr/bin/perl

use CGI;
use CGI::Carp 'fatalsToBrowser';
use DBD::mysql;
use Socket;

print "Content-type: text/html\n";
print "Expires: 0\n\n";

# Angaben zur Datenbank
$hostName = "localhost";    # Host auf dem die DB läft
$dataBase = "logdb";        # MySQL Datenbank
$userName = "root";         # MySQL Benutzer
$password = "secret";       # MySQL Password

# Werte lesen
$remote_addr = $ENV{'REMOTE_ADDR'};     # IP-Adresse
$user_agent  = $ENV{'HTTP_USER_AGENT'}; # Browserkennung
$referer     = $ENV{'HTTP_REFERER'};    # Referer
$req_uri     = $ENV{'REQUEST_URI'};     # URL mit dem SSI

# parametercheck fehlt noch

# Hostnamen ermitteln
$addr = inet_aton($remote_addr);
($name,$aliases,$atype,$len,$addrs) = gethostbyaddr($addr, AF_INET);

$SqlStatement = "INSERT INTO log (remote_addr, remote_host, url, user_agent, referer)
                VALUES ('$remote_addr', '$name', '$req_uri', '$user_agent', '$referer')";

$dbh = DBI->connect("DBI:mysql:$dataBase:$hostName", "$userName", "$password") ||
    die("Fehler beim Verbinden zur Datenbank: "$!);

my $sth = $dbh->prepare($SqlStatement) ||
    die("Fehler beim Vorbereiten des SQL-Statements: " . $dbh->errstr);

$sth->execute() ||
    die("Fehler beim Ausfuehren des SQL-Statements: " . $sth->errstr);

$sth->finish;
$rc = $dbh->disconnect;

# print "SSI-Counter - $SqlStatement";  # zum debuggen aktivieren

Das Script schreibt bei jedem Aufruf Datum, Uhrzeit, IP-Adresse, Hostname, URL, Referer und den User Agent in die Datenbank.

Normalerweise sucht der Webserver nur in Dateien mit einer speziellen Erweiterung (z.B. shtml) nach Server Side Includes. Mittels eines Eintrages in die Datei .htaccess kann man auch Dateien mit der Erweiterung html parsen lassen:

# .htaccess
Options Includes
AddType text/x-server-parsed-html .shtml
AddType text/x-server-parsed-html .html

Zum debuggen kann man print-Statements in das Script einbauen. Die Ausgaben werden an den Browser geleitet. Der fertige Scripte sollte dann keine Ausgaben mehr haben und ist damit im Browser und auch im Seitenquelltext unsichtbar.

Generierte Grafiken

Eine Alternative zu SSI ist das Einbinden einer dynamischen Grafik. In einer HTML-Datei sieht das dann so aus:

<HTML>
<HEAD>
<TITLE>Testseite</TITLE>
</HEAD>
<BODY>
<P>
Text Text Text

<img src="/cgi-bin/gifcounter.pl" border="0" width="0" hight="0">


oder auch so 

<img src="/cgi-bin/gifcounter.pl?www.meineurl.edu" border="0" width="0" hight="0">


</BODY>
</HTML>

Hier wird am Ende die Grafik gifcounter.pl eingefügt. Die Grafik wird dann im Browser angezeigt. Die aufrufende URL bekommt das Script über den Referer mitgeteilt. Manche Browser und Proxys unterdrücken die Übermittelung des Referers. Dann hilft nur noch das Eintragen eines Parameters mit "gifcounter.pl?www.meineurl.edu". Das Script schreibt dann den Parameter las URL ins Log.
Das folgende Script erzeugt eine 1x1 Pixel GIF-Datei:

#!/usr/bin/perl

use CGI::Carp 'fatalsToBrowser';
use DBD::mysql;
use Socket;

print "Content-Type: image/gif\n";
print "Expires: 0\n\n";

# create a 1x1 gif and print it
$gif = "GIF87a";
$gif .= chr(hex("01")).chr(hex("00")).chr(hex("01")).chr(hex("00")).chr(hex("80")).chr(hex("00")).chr(hex("00")).chr(hex("00"));
$gif .= chr(hex("00")).chr(hex("00"));
$gif .= chr(hex("ff")).chr(hex("ff")).chr(hex("ff")).chr(hex("2c")).chr(hex("00")).chr(hex("00")).chr(hex("00")).chr(hex("00"));
$gif .= chr(hex("01")).chr(hex("00")).chr(hex("01")).chr(hex("00")).chr(hex("00")).chr(hex("02")).chr(hex("02")).chr(hex("4c"));
$gif .= chr(hex("01")).chr(hex("00")).chr(hex("3B")).chr(hex("00"));

binmode STDOUT;
print "$gif";

# Angaben zur Datenbank
$hostName = "localhost";    # Host auf dem die DB läft
$dataBase = "logdb";        # MySQL Datenbank
$userName = "root";         # MySQL Benutzer
$password = "secret";       # MySQL Password

# parametercheck fehlt noch

$remote_addr = $ENV{'REMOTE_ADDR'};         # IP-Adresse
$user_agent  = $ENV{'HTTP_USER_AGENT'};     # Browserkennung
$referer     = $ENV{'HTTP_REFERER'};        # ist hier die url mit der Grafik
$query       = $ENV{'QUERY_STRING'};        # oder hier?

$addr = inet_aton($remote_addr);
($name,$aliases,$atype,$len,$addrs) = gethostbyaddr($addr, AF_INET);

# beim Einbinden einer Grafik steht im referer die aufrufende URL, wenn der Browser das verhindert
# hilft nur noch der Parameter aus dem query_string
if (!$referer) { $referer = $query; }

$SqlStatement = "INSERT INTO log (remote_addr, remote_host, user_agent, url)
                VALUES ('$remote_addr', '$name', '$user_agent', '$referer') ";

$dbh = DBI->connect("DBI:mysql:$dataBase:$hostName", "$userName", "$password") ||
    die("Fehler beim Verbinden zur Datenbank: "$!);

my $sth = $dbh->prepare($SqlStatement) ||
    die("Fehler beim Vorbereiten des SQL-Statements: " . $dbh->errstr);

$sth->execute() ||
    die("Fehler beim Ausfuehren des SQL-Statements: " . $sth->errstr);

$sth->finish;
$rc = $dbh->disconnect;

Da das Script eine Grafik ausgibt, sind keine weiteren prints zum debuggen machbar.

Auswertung der Daten

Man kann sich die erfassten Daten natürlich mit phpMyAdmin ansehen. Eleganter sind ein paar Scripte mit fertigen Reports.

Anzeige des Logs

#!/usr/bin/perl

use CGI::Carp 'fatalsToBrowser';
use DBD::mysql;

# Angaben zur Datenbank
$hostName = "localhost";    # Host auf dem die DB läft
$dataBase = "logdb";        # MySQL Datenbank
$userName = "root";         # MySQL Benutzer
$password = "secret";       # MySQL Password

print <<EOT;
Content-type: text/html


<HTML><HEAD><TITLE>log</TITLE>
</HEAD><BODY>

<h2>Log (Limit 10000)</h2>

<table border="1" width="100%" cellspacing="0" cellpadding="2" rules="all">
    <tr>
      <td><b>ID</b></td>
      <td><b>Time</b></td>
      <td><b>IP</b></td>
      <td><b>Host</b></td>
      <td><b>URL</b></td>
      <td><b>Referer</b></td>
      <td><b>Useragent</b></td>
    </tr>
EOT

# nur die ersten 10000 records
$SqlStatement = "SELECT id, timestamp, remote_addr, remote_host, url, user_agent, referer FROM log ORDER BY id ASC LIMIT 10000";

$dbh = DBI->connect("DBI:mysql:$dataBase:$hostName", "$userName", "$password") ||
    die("Fehler beim Verbinden zur Datenbank: "$!);

my $sth = $dbh->prepare($SqlStatement) ||
    die("Fehler beim Vorbereiten des SQL-Statements: " . $dbh->errstr);

$sth->execute() ||
    die("Fehler beim Ausfuehren des SQL-Statements: " . $sth->errstr);
            
$sth->bind_columns( undef, \$id, \$logtime, \$addr, \$host, \$url, \$user_agent, \$referer);

# liste aufbauen
while (@data = $sth->fetchrow_array()) {

# Timestamp in deutsches Datum umwandeln
$nicetime = substr($logtime,  6, 2) . "." .
            substr($logtime,  4, 2) . "." .
            substr($logtime,  0, 4) . " " .
            substr($logtime,  8, 2) . ":" .
            substr($logtime, 10, 2) . ":" .
            substr($logtime, 12, 2);

print <<EOT;
    <tr>
      <td nowrap>$id</td>
      <td nowrap>$nicetime</td>
      <td nowrap>$addr</td>
      <td nowrap>$host</td>
      <td nowrap>$url</td>
      <td nowrap>$referer</td>
      <td nowrap>$user_agent</td>
    </tr>
EOT

} # while

print "</table>";

$sth->finish;
$dbh->disconnect;

if ($sth->rows == 0) {
    print '<p><font color="#FF0000" size="3">Keine Treffer.</font></p>';
} else {

    $cnt = $sth->rows;
    print "<br><p><b>Anzahl: $cnt</b></p>";

}

print "</body></html>";

weitere Scripte zum Auswerten der Logdaten folgen...

 

 
© 2004-2023, network lab - we make your net work - Netzwerkforum
aktualisiert am 22.03.2012