#! /usr/bin/env python # -*- coding: Latin1 -*- import re import sys import psycopg from mx import DateTime class ParserError(Exception):     """     Ein 'ParserError' wird ausgelöst, falls während des Parsens einer     Logfile-Zeile ein Fehler auftritt.     """     pass SQL_STATEMENT = """   INSERT INTO statistics (     address,       userid,         datetime,         method,     url,           protocol,       status,           bytes,     referer,       agent   ) VALUES (     %(address)s,   %(userid)s,     '%(datetime)s',   %(method)s,     %(url)s,       %(protocol)s,   %(status)s,       %(bytes)s,     %(referer)s,   %(agent)s   )   """ class Logline:     """     Ein 'Logline'-Objekt enthält die folgenden Attribute:     address     (string)     # IP/hostname     userid      (string)     # authentifizierte User-ID     datetime    (DateTime)   # Zeitstempel     method      (string)     # HTTP-Methode, z. B. GET     url         (string)     # URL     protocol    (string)     # z. B. HTTP/1.1     status      (int)        # z. B. 200 or 404     bytes       (int)        # übertragene Bytes (None, falls unbekannt)     referer     (string)     # Referer (None, falls unbekannt)     agent       (string)     # HTTP-Client     Diese Attribute werden im Konstruktor gesetzt, sofern nicht ein     'ParserError' ausgelöst wird.     """     # regulärer Ausdruck, um eine Zeile des Logfiles zu parsen     _line_regex = re.compile(r"""       ^       (?P
\S+)       \s       \S+                       # ident ignorieren       \s       (?P\S+)       \s       \[           (?P               (?P\d\d) /               (?P\w{3}) /               (?P\d{4}) :               (?P\d\d) :               (?P\d\d) :               (?P\d\d)               \s               (?P[+-]\d\d)               00                # die letzten zwei Ziffern sollten stets 0 sein           )       \]       \s"                       # öffnendes Anführungszeichen       (?P\w+)       \s       (?P[^"]+)       \s       (?P[^"]+)       "\s                       # schließendes Anführungszeichen       (?P\d{3})       \s       (?P-|\d+)          # kann für best. Requests "-" sein       # optional, nur im "combined"-Format, nicht im "common"-Format       (?: E           \s           "(?P[^"]+)"  # Referer, in Anführungszeichen           \s           "(?P[^"]+)"    # Client, in Anführungszeichen       )?       $       """, re.VERBOSE)     _month_numbers = {       "Jan": 1, "Feb": 2, "Mar": 3, "Apr":  4, "May":  5, "Jun":  6,       "Jul": 7, "Aug": 8, "Sep": 9, "Oct": 10, "Nov": 11, "Dec": 12}     def __init__(self, line):         """         Parse eine Logfile-Zeile 'line' im "common"- oder "combined"-         Format und intialisiere diese Instanz entsprechend. Falls 'line'         nicht verarbeitet werden kann, erzeuge einen 'ParserError'.         """         match = self._line_regex.search(line)         if not match:             raise ParserError("can't parse line: %s" % line)         groups = match.groupdict()         # wandle einige der Gruppen in Ganzzahlen         for name in ['day', 'year', 'hour', 'minute', 'second',                      'timezone', 'status']:             # Umwandlung sollte wegen der "\d"s im reg. Ausdruck immer             #  funktionieren             groups[name] = int(groups[name])         # kopiere das Dictionary 'groups' in diese Instanz         self.__dict__.update(groups)         self.month = self._month_numbers[self.month]         # erstelle Zeitstempel         try:             self.datetime = DateTime.DateTime(                               self.year, self.month, self.day,                               self.hour, self.minute, self.second)         except DateTime.RangeError:             raise ParserError("invalid datetime: %s" % self.datetime)         self.datetime += DateTime.DateTimeDelta(0, self.timezone)         # erstelle User-ID         if self.userid == '-':             self.userid = None         # erstelle 'bytes'         if self.bytes == '-':             self.bytes = None         else:             self.bytes = int(self.bytes)         # erstelle Referer         if self.referer == '-':             self.referer = None     def save(self, connection):         """         Speichere die Daten aus dieser Instanz in der Datenbank.         Verwende dazu das Connection-Objekt 'connection'.         """         cursor = connection.cursor()         cursor.execute(SQL_STATEMENT, self.__dict__)         connection.commit()         cursor.close() def parse_and_save(filename):     """     Parse das Logfile und speichere die enthaltenen Daten in der     Datenbank.     """     logfile = open(filename)     connection = psycopg.connect("dbname=webstats user=schwa")     try:         for line in logfile:             try:                 logline = Logline(line)                 logline.save(connection)             except ParserError:                 pass     finally:         connection.close() parse_and_save(sys.argv[1])