Erstellung eigener Statistiken aus Apache-Logfiles
Veröffentlicht: 8. Juni 2015 (Zuletzt geändert am 25. Juli 2019)
Markus Meyer
0 Kommentare
Enthalten in 2 Kategorien: Website, Programmierung
Aktualisierung vom 19.07.2019: Die hier beschriebene Methode wird von mir derzeit nicht eingesetzt, da sie bei dynamisch generierten Webseiten durch WordPress nicht optimal funktioniert. Außerdem haben sich mittlerweile die Datenschutzregeln durch die DSGVO dahingehend verändert, dass kaum ein Hoster die IP der Besucher länger als 24 Stunden speichert.
Es gibt so einige Web-Analyse Tools im Internet, teilweise sogar richtig gute wie Google Web-Analytics. Aber all diese haben den Nachteil des Datenschutzes und der Performanceproblematik, wenn man die Grafiken gleich in die Webseite einbinden will. Mit Python und Goaccess kann man seine Logfiles auch einfach selbst in Grafiken verwandeln. In diesem Artikel erkläre anhand einiger Ausschnitte aus dem Python-Code, wie ich diesen Prozess automatisiert habe.
Ansatz
Das Ziel des Skript ist es, die verfügbaren Logfiles vom Webspace zu holen, zu verarbeiten und in Grafiken zu verwandeln. Dies muss mindestens wöchentlich passieren, da in meinem Fall mein Anbieter 1und1 alle Logfiles, die älter als 7 Tage sind anonymisiert. Dies tut er, indem er das letzte Byte der IP-Adressen auf ‘x’ ändert, was dazu führt, dass die Logfiles für jedes sinnvolle Auswertetool nutzlos sind. Das Ergebnis des Skripts sind Grafiken, wie sie auf der Statistikseite zu dieser Domain zu sehen sind. Folgende Schritte muss das Skript dazu abarbeiten:
- Hole alle Logfiles der letzten 7 Tage vom ftp server und speichere sie lokal ab
- Lösche alle lokal gespeicherten Logfiles, die älter als 1 Jahr sind (optional)
- Nutze Goaccess um die Logfiles auszuwerten und eine json-Datei zu erstellen
- Parse die json-Datei und extrahiere Daten für die Grafiken als Listen
- Erzeuge Grafiken für Besucher und Seitenaufrufe pro Monat, Infos zu Betriebssystem, Browser und Lage der Besucher sowie beliebte und referenzierte Seiten
- Lege die Grafiken an einen definierten Ort ab
- Führe das Skript alle 7 Tage selbständig automatisch aus
Zugriff auf die Logfiles über FTP
Über das Python-Modul ftplib
ist der Zugriff auf den FTP Server sehr simpel. Das Modul ist Teil der
Standardbibliothek, muss also nicht nachinstalliert werden. Zunächst
wird die FTP-Verbindung durch Angabe von URL und Zugangsdaten geöffnet.
Mit ftp.cwd("/logs")
wird in das Verzeichnis ‘logs’
gewechselt. In diesem Verzeichnis befinden sich die vom Anbieter
abgelegten Logfiles. In der nachfolgenden Schleife werden alle
Dateiennamen in der Liste filelist
in den aktuellen lokalen
Ordner kopiert. Da es sich in meinem Fall um gepackte .gz Dateien
handelt, muss die lokale Datei im Binärmodus erstellt werden.
import ftplib # ftp connection
def downloadLogsFromFtpServer(filelist):
# open ftp connection
ftp = ftplib.FTP('redpiramide.de', 'USERNAME', 'PASSWORD')
ftp.cwd("/logs") #changing to log directory
for filename in filelist:
localfile = open(filename, 'wb')
# download in binary mode
ftp.retrbinary('RETR '+ filename, localfile.write)
localfile.close()
ftp.quit()
Die Dateiliste enthält die nach dem von 1und1 definierten Namenschema benannten Dateien der letzten 7 Tage zum Zeitpunkt der Ausführung des Skripts. Mit ftp.quit()
wird die FTP-Verbindung sauber wieder geschlossen.
Auswertung mit Goaccess

Die Auswertung der heruntergeladenen Dateien geschieht mit dem Programm Goaccess. Dabei handelt es sich um ein Open-Source Tool, das eigentlich für die Erstellung von Statistiken in Echtzeit auf dem Webserver genutzt wird. Bei der Installation (auf einem Linux-System) gibt es nichts zu beachten. Folgen Sie einfach den Installationsanweisungen und beachten Sie, dass Sie ggf. die Abhängigkeiten vorher installieren müssen. Kompilieren Sie das Programm selbst, die Version in den üblichen Paketquellen sind teils deutlich veraltet. Nach der Installation müssen Sie ggf. das Format ihres Logfiles dem Programm mitteilen. In meinem Fall musste ich in der Datei /usr/local/etc/goaccess.conf
die Option log-format %h %^[%d:%t %^] "%r" %s %b %^ "%R" "%u"
mit einem zusätzlichen ignoriertem Feld (%^
)) anpassen, da 1und1 an dieser Stelle die eigene Domain einfügt.
In unserem Fall starten wir den Auswerteprozess einmalig für alle gepackten Logfiles (.gz Dateien) im aktuellen Ordner. Zum Start des Programms aus Python wird das, ebenfalls zur Standardbibliothek gehörende, Modul subprocess
genutzt. Durch die Option shell=True
wird der erste Parameter von call()
in der Kommandozeile ausgeführt.
import subprocess # call other applications as process
goaccess = subprocess.call("zcat -f access.log.*.gz | goaccess --real-os
--ignore-crawlers -c -o json > report.json", shell=True)
Die Ergebnisse der Auswertung werden im json-Format in die Datei report.json
umgeleitet. Im nächsten Schritt muss diese Datei im Python-Skript ausgewertet werden.
Parsen der erzeugten json-Datei
Auch für diesen Zweck bietet Python eine Standardbibliothek namens json
an, die die Weiterverarbeitung sehr vereinfacht. Nachdem die Datei geöffnet wurde, wandelt die Codezeile data = json.load(jsonFile)
den Inhalt der Datei in ein Python Dictionary names data
um. Der Zugriff auf die Elemente erfolgt dann direkt über den
entsprechenden Key. Ein Beispiel. Der nachfolgende Ausschnitt aus der
json-Datei gibt die Besucher (visitors
) und Seitenaufrufe (hits
) pro Tag an. Der Tag ist dabei im Element data
codiert. Die Daten 20150607
entsprechen dem 07.06.2015. Die Angaben wiederholen sich für jeden Tag,
der in den Logfiles vorkommt. Für ein Jahr also maximal 365 mal.
[...]
"visitors": [
{
"hits": 7,
"visitors": 4,
"percent": 0.92,
"bytes": 22773,
"data": "20150607"
},
{
"hits": 2,
"visitors": 2,
"percent": 0.26,
"bytes": 10458,
"data": "20150606"
},
[...]
In der Schleife for visitor in data["visitors"]:
werden alle diese einzelnen Tage durchlaufen. In die Variable m
wird die Monatszahl aus dem data
-Element gezogen. Diese dient als Index um alle Seitenaufrufe (visitor["hits"]
) eines Monats aufzuaddieren und in der Liste hitsPerMonth
zu speichern. Analog geschieht dieser Vorgang mit den Besuchern. Diese Listen werden später für die Grafiken benötigt.
import json # encode and decode json files
months = ["Jan.", "Feb.", u"März", "April", "Mai", "Juni", "Juli", "August",
"Sept.", "Okt.", "Nov.", "Dez."]
hitsPerMonth = [0] * len(months)
visitorsPerMonth = [0] * len(months)
with open('report.json') as jsonFile:
data = json.load(jsonFile) # loads the whole file to python dictonaries
for visitor in data["visitors"]:
m = int(visitor["data"][4:6])-1 # month
hitsPerMonth[m] += visitor["hits"]
visitorsPerMonth[m] += visitor["visitors"]
Ein anderes Beispiel ist das Extrahieren der Herkunft der Besucher. Hier addiert Goaccess die Besucher und Seitenaufrufe über alle eingelesenen Logfiles automatisch. Wir müssen an dieser Stelle nur alle items
im Element geolocation
durchgehen und die Daten in eine eigene Liste (listOfTopLocations
) von Dictionaries (topLocations
) kopieren. Anschließend erfolgt die Sortierung dieser Dictonaries absteigend nach Seitenaufrufen. Um eine Liste von Dictonaries einfach nach einem speziellen Key zu sortieren, wird das Modul itemgetter
genutzt. Die enstehende Liste wird später für die Erstellung eines Balkendiagramms genutzt.
from operator import itemgetter # to sort a dict by specific key
for location in data["geolocation"]:
for country in location["items"]:
topLocations = {"name" : "" , "hits" : 0, "visitors" : 0 }
topLocations["name"] = country["data"]
topLocations["hits"] = country["hits"]
topLocations["visitors"] = country["visitors"]
listOfTopLocations.append(topLocations)
listOfTopLocations = sorted(listOfTopLocations, key=itemgetter("hits"))
Erzeugen von Grafiken

Für die Umwandlung der extrahierten Listen in einen Graphen wird die sehr beliebte, vielseitige und mächtige matplotlib
eingesetzt. Dieses Modul gehört nicht zur Standardbibliothek, kann aber über den Distributions-Paketmanager (z.B. apt-get) oder über den Python eigenen Paketmanager (pip oder easy-install) nachinstalliert werden. Matplotlib benötigt zwingend das Modul numpy
. Im Normalfall installiert der Paketmanager alle Abhängigkeiten mit.
Der folgende Codeausschnitt zeigt meine Implementierung zur Erzeugung eines Balkendiagramms für Besucher und Seitenaufrufe pro Monat über das letzte Jahr. Dabei werden die beiden Balken pro Monat nebeneinander dargestellt. Zusätzlich wird die absolute Anzahl pro Monat in den beiden letzten Schleifen direkt über die Balken geschrieben. Für die automatische Positionierung des Gitternetzes wird die Klasse AutoMinorLocator
verwendet. Das fertige Plot wird über plt.savefig(filename, dpi=100)
in eine Date mit dem Namen filename
abgespeichert. Beachten Sie, dass für die Speicherung von .jpg oder .png files die entsprechenden Bibliotheken im System vorhanden sein müssen. Zusätzlich ist es ggf. notwendig den Renderer auf Agg
zu setzen, damit die Speicherung auch ohne angeschlossenes Display (z.B. beim Zugriff über SSH) fehlerfrei durchläuft. Dazu können Sie entweder import matplotlib
und in der nächsten Zeile matplotlib.use('Agg')
vor dem Import von matplotlib.pyplot
schreiben oder den Standard-Renderer in der Datei .matplotlibrc
einstellen.
import numpy as np # used by matplotlib
import matplotlib.pyplot as plt # for creating plots/charts etc.
from matplotlib.ticker import AutoMinorLocator
def createMonthyDualPlot(y1, y2, title, filename):
n_groups = len(y1)
bar_width = 0.4
fig, ax = plt.subplots()
fig.set_size_inches(20,10)
plt.rcParams.update({'font.size': 22})
index = np.arange(n_groups)
rects1 = plt.bar(index, y1, bar_width,
alpha=0.6,
color='r',
label='Aufrufe')
rects2 = plt.bar(index + bar_width, y2, bar_width,
alpha=0.6,
color='b',
label='Besucher')
plt.xlabel(u'Monat')
plt.ylabel(u'Anzahl')
plt.title(title)
plt.xticks(index+bar_width, months)
plt.rc('legend',**{'fontsize':16 , 'loc':'lower right'})
plt.legend()
ax.yaxis.set_minor_locator(AutoMinorLocator())
ax.yaxis.grid(True, which='both')
for rect in rects1:
height = rect.get_height()
if height > 0:
ax.text(rect.get_x()+rect.get_width()/2., height,
'%d'%int(height), ha='center', va='bottom')
for rect in rects2:
height = rect.get_height()
if height > 0:
ax.text(rect.get_x()+rect.get_width()/2., height,
'%d'%int(height), ha='center', va='bottom')
plt.tight_layout()
plt.savefig(filename, dpi=100)
Wöchentliche Ausführung
Das Skript muss mindestens alle 7 Tage ausgeführt werden, damit es lückenlos die Daten in den Logfiles nutzen kann. Damit man dies nicht manuell tun muss und so so ggf. vergisst, empfiehlt es sich hier einen sogenannten Cronjob anzulegen. Am Einfachsten ist dies durch Verlinken oder Aufrufen des Skriptes im Ordner /etc/cron.weekly
und Nutzung von Anacron. Achten Sie darauf, dass Dateinamen in diesem Ordner keine Sonderzeichen (auch kein Leerzeichen) sowie keine Dateiendung enthalten dürfen. Ich nutze meinen Raspberry-Pi für die regelmäßige Abarbeitung des Skripts. Dazu habe ich in o.g. Ordner ein kleines Shell-Skript abgelegt, dass in den Nutzerordner wechselt und dort das Programm python
mit dem Dateinamen des Skripts aufruft. Das funktioniert super.
Damit bin ich am Ende dieses Artikels. Wie das Endergebnis aussehen kann, sehen Sie auf meiner Statistikseite. Ich hoffe Sie hatten Spaß beim Lesen und Erfolg beim Umsetzen des Geschriebenen. Falls Sie Hilfe benötigen, schreiben Sie mich doch einfach an. Vielen Dank.