+++
title = "Accesslogs in die Datenbank"
date = "2011-06-21T16:30:00+00:00"
author = "Gibheer"
draft = false
+++

Da ich mal unsere access-logs der letzten paar Tage auswerten wollte,
brauchte ich eine Moeglichkeit die Daten in eine Datenbank zu packen.
Dummerweise sind die Log-Dateien der Webserver nicht Datenbankgerecht
und keiner hat ein Interface um Datensaetze gleich in eine Datenbank zu
schreiben.

Deswegen hab ich mir eine Methode ueberlegt, mit der ich die Daten in
die Datenbank bekomme, ohne dass es viel Aufwand bedarf.

Voraussetzungen
===============

Da das hier eine Art kleines Tutorial wird hier eine Liste der Programme
die ich benutzt habe:

\* lighttpd\
 \* PostgreSQL\
 \* simples Shell (sh FreeBSD Version)\
 \* cron\
 \* logrotate (newsyslog in FreeBSD)

Logformat setzen
================

Als erstes brauche ich ein Logformat, welches sich auch gut verarbeiten
laesst. Im Default sind alle Felder durch Whitespaces getrennt und es
gibt Felder, welche nicht immer gefuellt sind, was sich nur sehr schwer
parsen laesst.

Deswegen hatte ich mir ueberlegt, das Logformat in Richtung CSV-Datei
auszurichten. Im lighttpd muss dazu das Module `mod_accesslog`
eingebunden werden.

Danach steht die Option
`accesslog.format`:http://redmine.lighttpd.net/wiki/1/Docs:ModAccesslog
zur Verfuegung. Diese habe ich bei uns auf folgenden Wert gesetzt

<source>\# log format for csv\
\# h host ip
\# t timestamp of the end of the request\
\# m request method
\# s status code\
\# b bytes sent
\# U request URL\
\# f real filename
\# q query string\
\# T time used in secounds
accesslog.format = "h|t|m|s|b|U|f|q|T|i|{Referer}i"\
</source>

Das Zeichen `|` ist dabei mein CSV-Trennzeichen, damit ich die Werte
auseinander halten kann.

Datenbankschema
===============

Das Datenbankschema entspricht dem Schema der CSV-Datei.

<source:sql>CREATE TABLE logs (\
 ip text,\
 datum text,\
 method text,\
 code integer,\
 bytes integer,\
 req\_file text,\
 full\_path text,\
 query\_string text,\
 time\_taken integer,\
 user\_agent text,\
 referer text\
);\
</source>

optionaler User
---------------

Wenn man auf Nummer sicher gehen will, kann man noch einen zusaetzlichen
User anlegen, welcher nur Insertrechte auf diese Tabelle hat. Damit kann
man ausschliessen, dass irgendwas doofes passiert, wie zum Beispiel ein
Drop Table oder aehnliches.

Das geht dann mit folgendem Befehl:

<source:sql>create role wwwimport login password
’changeme![](';</source>

h1. Logrotate

Lighttpd schreibt per default nach @/var/log/lighttpd/access.log@ bis kein Speicher mehr da ist. Wenn man allerdings die Datei allerdings verschiebt und eine neue hinstellt ohne den Server neu zu starten, so loggt dieser in die verschobene Datei. Wird sie geloescht, dann loggt der Server garnicht mehr.
Da das fuer die Logverarbeitung ungeschickt ist, musste da irgendwie ein logrotate rein. Unter FreeBSD geht das mit @/etc/newsyslog.conf@ wo ich den Eintrag

<source>/var/log/lighttpd/access.log www:www 664 7 * @T00 Z /var/run/lighttpd.pid</source>

gesetzt habe. Das erstellt Archive im Format @access.log.<counter>.gz@. Allerdings wird dabei auch am Anfang der neuen und am Ende der alten Datei eine Zeile eingesetzt, dass ein rotate stattgefunden hat.

h1. das Script als cron

Um die Daten in die Datenbank zu bekommen habe ich den Befehl "COPY":http://www.postgresql.org/docs/9.0/static/sql-copy.html genommen, da er schneller als 9000 einzelne Selects arbeitet.
Zusaetzlich hat der Timestamp den kleinen Makel, dass er von [ und ] umgeben ist, wodurch sich das Feld nicht einfach als Timestamp speichern laesst.
Die Timestamps lassen sich allerdings recht einfach mit folgendem Befehl bereinigen

<source:sh>sed -e "s/[][]//g"</source>

Das Problem mit der zusaetzlichen Zeile loesen wir mit einem @grep@.

<source:sh>grep -v "logfile turned over"</source>

geloest. Das gibt nur noch die Zeilen aus, welche nicht den String enthalten.

Insgesamt kommt dabei bei mir folgendes kleines Script raus.

<source:ksh>#)/usr/bin/env ksh

LOG\_PATH=“/var/log/lighttpd”\
LOG\_FILE=“access.log”\
TABLE=“logs”\
DATABASE=“test”\
DBUSER=“wwwimport”\
DBPASS=“changeme![]("

WORKDIR="/tmp/logimport"

# create a workdir
if [ -d $WORKDIR ]; then
  echo 'Job is running)’\
 exit 1\
else\
 mkdir ~~m 700 \$WORKDIR\
fi
\
files=\$\
workfile="\${WORKDIR}/dump.sql"
\
echo "COPY \${TABLE} FROM STDIN WITH DELIMITER ‘|’;" \>\> \$workfile
\
for file in \$files; do\
 gzcat \$file | sed~~e”s/[][]//g" | grep ~~v “logfile turned over” \>\>
\$workfile\
 rm \$file\
done
\
PGPASSWORD=“\$DBPASS” psql~~U \$DBUSER -f \$workfile \$DATABASE

1.  delete the workdir\
    rm \$workfile\
    rmdir \$WORKDIR\
    exit 0</source>

Das habe ich dann noch irgendwo gespeichert und im cron mit

<source>0 23 \* \* \* sh logimport.sh</source>

eingetragen. Jetzt importiert es munter meine Logs und ich kann anfangen
`R` zu lernen.
