Perl Webworking

Apache Logs auf zentralen Loghost verwalten

Das Speichern und Verwalten von Webserver-Zugriffslogs ist trivial:  Man nutzt einfach die Directiven CustomLog und Errorlog im Apache und alles ist erledigt. Als ganz schlauer Admin mit SLES11 ruft man vielleicht noch eben ein
zypper -install webalizer
auf und schon hat man auch noch die Webstatistik impetto.

Gut. Das klappt wirklich so. Nämlich dann, wenn man auf einer Linuxkiste arbeitet und man vielleicht ein paar wenige, selbst verwaltete Webauftritte hat, die auch nicht so hohe Zugriffszahlen aufweisen…

Schluss mit dem Kinderkram!

Meine Ausgangslage sieht aber dann doch etwas anderes aus: Ich betreibe auf 5 Webservern über 600 Webauftritte von einigen Hundert verschiedener Kunden. Diese Webauftritte bestreiten das gesamte Spektrum von einfach bis komplex: Es gibt Webauftritte aus einer handvoll statischer Webseiten genauso wie Webauftritte die mit eigenen CMS- und Redaktionssystemen betrieben werden. (Dabei auch Performancefresser wie Typo3 und WordPress). Auch bei den Webauftritte die nicht auf Basis eines CMS oder Redaktionssystems funktionieren, verwendet die überwiegende Mehrzahl interaktive Skripten (PHP, Perl, Pyton, …).
Einige der Webauftritte verursachen mehrere Hundert GigabyteTraffik pro Monat. Bei ein paar dieser Webauftritte sind die Accesslogfiles pro Monat stets größer als 2 GB. Alles zusammen verzeichnet die Statistik etwa 1 Terabyte Traffik und 37 Millionen Zugriffe im Monat. Und ja, das ganze läuft wirklich  auf nur 5 Servern (und zwar keine teuren Spezialserver, sondern inzwischen ältern Sunserver vom Typ SunFire T2000).

Das Ziel

Durch einen Load-Balancer soll ein Webauftritt von einem der 5 Server bedient werden. Dabei ist selbstverständlich vorher nicht klar welcher Webauftritt durch welchen Server behandelt wird. Jeder Server kann den Request annehmen und bearbeiten. Und beim nächsten Aufruf kann es der nächste Server sein.
Vom Filesystem her arbeiten alle auf denselben Bereich. (Wie das Fileystem redundant organisiert wird ist eine andere Geschichte).
Da jeder Webserver die Zugriffe bearbeiten kann, ist eine lokale Speicherung der Zugriffslogs ungeschickt. Sowohl ein Webmaster einer Domain der etwas nachprüfen will, als auch die automatisierte Auswertung mittels eines Statistikproggrammes wie Webalizer verlangen eine Datei, die alle Zugriffe auf den Webauftritte enthalten. Niemand will auf jedem einzelnen Server nachschauen müssen ob und was da etwa noch steht.
(Ganz abgesehen davon, daß meine Kunden gar keine Zugriffsberechtigung auf die Webserver bekommen).
Ziel ist also: Jeder Webauftritt braucht eine eigene Zugriffslog, die alle Zugriffe verzeichnet, egal welcher Webserver konkret den Request bearbeitete.
Und unter Umständen wird nicht nur die Zugriffslog benötigt, sondern auch die Errorlog.

Weitere Anforderungen:

Die Server sollen nicht auf eine technologische Basis beschränkt sein. Neben den jetzigen 5 Sun-Servern sollen auch andere Server mit anderen Betriebssystemen (SLES10, SLES11, Ubuntu, u.a.) zentral verwaltete Logdateien erstellen.
In Hinblick auf Bedenken des Datenschutzes hinsichtlich der Personenbeziehbarkeit von IP-Adressen sollten diese Rechneradressen anonymisiert werden, soweit dies möglich ist.

Umsetzung

Die Umsetzung erfolgt mit Hilfe eines zentralen Loghosts. Dieser nimmt alle Logmeldungen mit syslog-ng entgegen und schreibt sie in die Zieldateien. Die Webserver spielen dabei die Rolle der „Clients“. Diese senden die Nachrichten über syslog oder syslog-ng an den Loghost.
Bevor ich auf die Apache-Konfiguration eingehe, zunächst zur Konfiguration der Syslog-Dämonen.

Die Syslog-Clients: Die Webserver

Je nachdem auf welchem Betriebssystem gearbeitet wird, steht auf unseren Server entweder syslog oder syslog-ng zur Verfügung. Für den Loghost macht es keinen Unterschied, welche Software die Clients nutzen.

Clients mit syslog

Clients mit dem normalen Syslog brauchen folgende Zeilen in der Konfiguration syslog.conf:

Die Facilities local0, local2 werden verwendet für Errorlogs, während die Facilities local1 und local3 für Access-Logs genutzt werden.
Warum wir jeweils zwei Facilities verwenden erkläre ich weiter unten.

Clients mit syslog-ng

Wir modifizieren die Default syslog.conf um folgende Einträge:

Modifizierter Options-Bereich oben:

Innerhalb der üblichen Filter:

Und neu am Ende:

Der Loghost

Mit folgender Konfiguration des syslog-ng’s wird dafür gesorgt, dass

  • Access-Logdateien auf taeglicher Basis
  • Errorlog-Dateien auf monatlicher Basis

entstehen. Die Dateien werden dabei in ein Logverzeichnis ( /proj.stand/log/access und /proj.stand/log/errors/ ) geschrieben. Im Falle der Errorlogs in ein Verzeichnis pro Monat im Falle der Accesslogs in ein Verzeichnis $Monat/$Tag.
Ein Aufräumskript sorgt dafuer, dass die Logdateien nach einer gewissen Zeit gelöscht werden.

Server-Konfiguration in /etc/syslog-ng/syslog.conf:

Hier definieren wir die Quelle: Wir betrachten Syslogmessages die sowohl über UDP als auch über TCP kommen.

Diese drei Anweisungen definierten das Format der Messages wie sie gespeichert werden sollen.

Die Destinations definieren wohin wir Zugriffe speichern wollen und welches Format diese dabei haben.
Zu beachten ist auch hier, daß ich im Falle der Error- und der Accesslogs jeweils zwei Varianten betrachte: destination d_apacheaccess vs. destination d_sn_apacheaccess und destination d_apacheerror vs. destination d_sn_apacheerror.
Im ersten Fall speichere ich die Logmessages direkt in eine Datei, in zweiten Fall sende ich die Messages an ein Programm. (Siehe weiter unten).

Die beiden letzten Destinations dienen nur noch zu Kontrollzwecken. Mit der Destination d_hostactivity{}, welche Server konkret Apache-Anfragen senden und also aktiv sind.
Damit kann ich beim Einsatz des Webclusters feststellen, ob einzelne Server überhaupt aktiv sind und wie häufig sie es in Relation zu anderen Servern sind. (Dadurch kann ich wiederrum Rueckschlüsse auf das LoadBalancing fuehren.)
Die Destination netaccess nutz ich dagegen um solche Hosts abzufangen, die Nachrichten an meinen Loghost senden, aber nicht in dem erlaubten Subnetz IP.WEBSERVER.SUBNETZ.0 sind.

Nun, da ich weiss, wohin gespeichert wird, muss ich noch die Filter definieren:

Die Filter definieren Bedingungen die in der folgende Verarbeitung des Syslogstreams zum Zuge kommen.
IP.WEBSERVER.SUBNETZ.0 enthält natürlich die IP-Adresse des Subnetzes in dem die eigenen Server stehen.
Wenn die jeweilige Filterbedingung erfüllt ist, dann wird die Messages an die Destination weitergeleitet:

Damit ist die Syslog-Konfiguration für den Loghost vollständig.
(Bitte abernoch weiterlesen – bevor wir den syslog-ng restarten brauchen wir noch 2 Perlskripten. Diese hab ich weiter unten beschrieben).

Zur Erklärung der Facilities: Wie oben bereits geschrieben, werden die Facilities local0, local2 verwendet für Errorlogs, während die Facilities local1 und local3 für Access-Logs genutzt werden.

Warum aber die umständliche Trennung in jeweils zwei Paare mit weiteren Facilities? Normalerweise sollten doch local0 und local1 ausreichen. Denn so schreiben es ja schliesslich auch viele andere Dokumentation im Web zum Thema.
Die Antwort wird ersichtlich, wenn man an das Anfangsszenario denkt und dieses mal in den Apache-Webserver umsetzt. Wenn wir normalerweise für jeden Virtual Host eine eigene Acesslog und eine eigene Errorlog definieren wollen, tun wir dies wie folgt:

Was passiert nun, wenn wir dies in einem Apache eintragen, der über 600 Webauftritte verwaltet?
Die Antwort: Beim Start des Apaches werden nun nur für das Logging 1200 Prozesse gestartet, die während der Laufzeit des Apaches mitlaufen!
Damit ist klar, daß man diese Variante für richtiges MassVirtualHosting vergessen kann.
Wir brauchen dort eine bessere, performanceschonendere Lösung.
Wobei wir aber trotzdem diese Variante für „kleine“ Server mit nur einem oder wenigen Webauftriotten trotzdem aktiviert lassen wollen.
Die Lösung: Wir definieren die Facilities local0 und local1 dafür auf diese Weise Logfiles zu sichern.
Für Apaches mit vielen Virtual Hosts, die auch alle geloggt werden sollen dagegen, nutzen wir local2 und local3 und machen die Log-Directiven in der Apache-Konfiguration anders.

Die Apache-Konfigurationen

Apache Konfiguration für Webserver mit Logdefinition pro VHOST (wenige VHOSTs)

Die Logs werden in den einzelnen VHOST-Eintraegen definiert.

Vorher wird das Logformat wie folgt global festgelegt:

Durch die Einträge werden beim Start des Apache-Servers zwei Prozesse mit den Skripten /usr/bin/apache-access-logger.pl und /usr/bin/apache-error-logger.pl aufgerufen.

Dabei handelt es sich um zwei simple Perlskripten.

apache-access-logger.pl

Es gibt eine rege Diskussion darüber ob die IP-Adresse, bzw. der Rechnername im Sinne des Datenschutzes erfasst werden darf oder nicht. Diese Diskussion soll jedoch hier nicht weiter ausgefuehrt werden.
Das obige Skript kürzt im Falle einer IP-Adresse diese ab dadurch das die letzte Ziffer auf 0 gesetzt wird. Falls dies weitergehenden Fordungen nicht genüge tut, kann die Regular Expression auch wie folgt geaendert werden:

(Vgl: auch: ZENDAS: Erstellung anonymisierter Apache Logfiles)
Im Falle dass ein Rechnername angegeben ist, wird diese bis auf die Subdomain gekürzt.

apache-error-logger.pl

Anmerkung: Im Falle von Fehlern anonymisiere ich die Rechneradressen nicht! Denn Fehler sollten garnicht auftreten. Wenn diese auftreten, ist es notwendig alle Daten der Requests zu haben. Auch die vollständige IP-Adresse.

Apache Konfiguration für Webserver mit globaler Logdefinition (viele VHOSTs)

In dieser Variante enthalten die einzelnen VHOSTs keine eigene Log-Directiven. (Man kann dies aber durchaus trotzdem tun und somit beide Verfahren mischen. Dies macht zum Beispiel dann Sinn, wenn man fuer einzelne VHOSTS eine eigene Errorlog haben moechte).

Der Aufruf im Apache erfolgt über die globale Definition in solcher Form:

Auch hier hab ich zwei Perlskripten erstellt. Diese unterschieden sich von den beiden obigen nur dadurch, daß ich hier auf die Anonymisierung verzichte und andere Facilities angebe. (Die Anonymisierung erfolgt auf der Loghost-Seite, damit ich auf der Seite der Webserver etwas Performance spare. Selbst wenn es nur Mikrosekunden sind… ).

apache-vhostaccess-logger.pl

apache-vhosterror-logger.pl

Filter-Skripte auf Loghost für Apache Mass-VHosting

Auf dem LogHost müssen nun noch die Filterskripte definiert werden, die alle Nachrichten der Webserver erhalten.
Syslog-ng kann leider keine Ergebnisse von RegExps in dessen Filtern als Variable weiterverarbeiten. Deswegen wird dann als „Destination Driver“ program() verwendet: „program() Forks and launches the specified program, and sends messages to its standard input.
Diese Nachrichten enthalten im Falle der Accesslogs Dank der LogFormat-Angabe in der ersten Spalte die Angabe des Namen des Webauftritts.
Danach wird gefiltert.

dest-access-filter.pl

Zu Erwähnen sind die Timeouts und die Prüfung auf den Tageswechsel. Ebenso wie der Apache startet Syslog den Filterprozess einmal mit den beim Start geltenden Argumenten.
Wenn der Prozess nicht gestoppt wird, läuft dieser Prozess immer weiter. Auch über den Tageswechsel hinaus. Logmeldungen werden dann also ggf. in die falsche Datei gespeichert. Aus diesem Grund, und um zu viele offene Filehandles im Memory zu vermeiden, lass ich das Skript in regelmässigen Abständen und zum Tageswechsel sich selbst beenden. Syslog-NG wird dann den Prozess selbstständig neu starten. Dann aber mit jeweils aktuellen Parametern.

dest-error-filter.pl

Wie auch das Skript für die Accesslogs enthält der Filter für die Errorlogs ein Timeout und ein Exit bei dem Tageswechsel.
(Ich verwalte in diesem Skript die Dateihandles etwas anders als bei den Accesslogs, dies tut aber nichts zur Sache und hat als Grund nur das ich an der Stelle später besser diverse Debugausgaben einbauen kann).

Hinweise

Errorlog bei zentralen Logfiles

  • Der Filter für globale Errorslogs filtert den $vhost-Namen optional nicht aus dem Logstream aus, da bei den Einsatz mit Load-Balancern es wichtig sein kann zu wissen, auf welchem Server ein Fehler passierte. Bei Access-Logs dagegen ist dies nicht notwendig.
  • Bei globalen Errorlogs ist die Syntax etwas anders als bei Errorlogs in Virtual Hosts. Eine LogFormat-Directive für Errorlogs gibt es nicht. Aus der Manual:
    It is not possible to customize the error log by adding or removing information. However, error log entries dealing with particular requests have corresponding entries in the access log. For example, the above example entry corresponds to an access log entry with status code 403. Since it is possible to customize the access log, you can obtain more information about error conditions using that log file.
  • Im Falle von Verbindungen die über einen VHOST kommen der SSL verarbeitet, wird die Anfrage jedoch um den Servernamen ergänzt. Diese können wir nutzen, um doch noch wenigstens in diesen Fällen zu filtern.

    Beispiel eines Errorlog-Streams der beim Skript ankommt:

    Clients auf SLES

    Auf SLES10 erfolgt die Bearbeitung der syslog.conf in der Datei /etc/syslog-ng/syslog.conf.in .
    Die Änderungen werden danach aktiviert durch den Befehl
    SuSEconfig --module syslog-ng

    Auf SLES11 dagegen wird direkt in der /etc/syslog-ng/syslog.conf geändert und der Dämon schlicht über /etc/init.d/syslog restart neu gestartet.

    Clients auf Solaris-Server

    Per Default dendet der Syslog unter Solaris (seit SunOS 5.6 (Solaris 2.5.1) ) nachrichten im Format:

    [ID msgid facility.priority] Nachricht

    Beispiel:

    Diese kommen auf dem Loghost dann in der $MSGONLY als solches an. Syslog-NG kann zwar danach filtern mit einer RegExp, jedoch kann syslog-ng den ID-Bestandteil leider nicht selbst entfernen.

    Es gibt zwei Möglichkeiten für SUN-Admins:

    1. Man installiere syslog-ng auf dem Client
    2. Man konfiguriert das Format und hofft, das dieser Folgepatches überlebt ;)

    Ich hab mich für letzteres entschieden. Denn da wir cfengine verwenden um unsere Konfiguration zu verwalten, dürfte damit auch bei wildgewordenen Patches nichts passieren.
    Die Konfigurationsdatei für Nachrichtenformate in syslog unter Solaris ist die Datei
    /kernel/drv/log.conf

    Sie sieht per Default so aus:


    Zum Abschalten der MSGId ist der Wert
    msgid=1;
    einfach auf „0“ zu setzen. Damit diese Einstellung aktiv wird, ist ein Reboot notwendig.

Weitere Links und Quellen

1 Kommentar zu “Apache Logs auf zentralen Loghost verwalten