Logfile in eine MSSQL Datenbank einlesen

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@fredvonfat: dann arbeite Dich erst an einfachen Beispielen ins re-Modul ein, dann lernst Du, wie Du es anwenden kannst, und kannst danach Dich auch an kompliziertere Ausdrücke wagen.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Na ja, mein zu letzt geposteter Schnipsel sehe ich jetzt nicht als so kompliziert an, es ist auch nicht so, dass die regex mir völlig fremd sind, nur unter Python hatte ich damit noch nichts zu tun.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

daher meine Empfehlung sich in das re-Modul einzuarbeiten.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Ich bin nun schon ein kleines Stück weiter gekommen, dank eurer Hilfe habe ich auch die DB-Struktur noch einmal überdacht.
Eine Tabelle sollte reichen und die exclusionen habe ich auch rausgenommen, dass ist dann ein Job für die Datenbankabfrage.
Zunächst denke ich, dass es besser ist erst einmal alles mitzunehmen.

Auf dem Weg, den Code zu lernen bin ich nun bei den Funktionen bzw. deren Verkettungen angelangt.
Bevor ich mich an die Datenbank mache würde ich mir die Daten gerne ausgeben lassen.

Allerdings bekomme ich immer ein
<generator object parse_log at 0x7f70b388be08>
Was hab ich noch nicht verstanden bzw. wo kann ich ansetzen um das zu ändern?

Code: Alles auswählen

#!/usr/bin/env python3
#
#
#
#
#
#
import os
import sys
import re
from collections import namedtuple
from contextlib import closing
from datetime import datetime as DateTime

import pymssql

LOG_PATH = '/var/log/ESI/ESI2'
LOG_FILENAME_TEMPLATE = os.path.join(LOG_PATH, 'F1log_{}.txt')

COMMON_PATTERN = r'^(\d+) (?P<zeit>\d{2}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) '
MAIN_RE = re.compile(COMMON_PATTERN + r'(?P<prom>\d{8}) (?P<message>.{14}) .+ N00 (?P<did>\d{4}) (?P<rufnummer>\d+)? ?#')
ERROR_RE = re.compile(COMMON_PATTERN + r'ISDN Fehlanruf C.00 (?P<did>\d+) (?P<rufnummer>\d+)? ?Diag:(?P<message>.{14})')

Main = namedtuple('Main', 'zeit prom message did rufnummer')
Error = namedtuple('Error', 'zeit prom message did rufnummer')

       
def get_common(match):
    zeit = DateTime.strptime(match.group('zeit'), '%d/%m/%y %H:%M:%S')
    did = int(match.group('did'))
    rufnummer = match.group('rufnummer')
    return (zeit, did, rufnummer)

def parse_log(lines):
    for line in  lines:
        match = MAIN_RE.match(line)
        if match:
            zeit, did, rufnummer = get_common(match)
            message = match.group('message')
            prom = match.group('prom')
            yield Main(zeit, prom, message, did, rufnummer)
        else:
            match = ERROR_RE.match(line)
            if match:
                zeit, did, rufnummer = get_common(match)
                prom = 'x{8}'
                message = match.group('message')
                yield Error(zeit, prom, message, did, rufnummer)

def main():
    log_filename = LOG_FILENAME_TEMPLATE.format(sys.argv[1])
    with open (log_filename) as lines:
        print(parse_log(lines))

main()
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@fredvonfat: `parse_log()` ist eine Generatorfunktion (wegen ``yield``) — die macht beim Aufruf nichts ausser sofort ein Generator-Objekt zu liefern. Die Arbeit passiert erst wenn man die Elemente von diesem Generator abfragt. Also entweder schreibst Du da eine Schleife drüber und gibts die einzelnen Elemente aus, oder Du rufst `list()` mit dem Generator auf, damit die Elemente alle in einer Liste gesammelt werden, und gibst dann die Liste aus.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Das mit dem 'list()', war ein sehr guter tip. Die testweise Ausgabe funktioniert jetzt.

Allerdings habe ich gerade ein Problem mit pymssql.

folgender Code liefert keine Fehlermeldung, legt aber auch keine Tabelle an.

Code: Alles auswählen

#!/usr/bin/env python3

import os
import re
import sys
import pymssql
from contextlib import closing


conn = pymssql.connect(
        server='localhost',
        user='sa',
        password='xxx',
        database='F1'
        )

with closing(conn.cursor()) as cursor:
    cursor.execute("""
        CREATE TABLE main(
        zeit DATETIME,
        prom VARCHAR(8),
        message VARCHAR(14),
        did INTEGER,
        rufnummer VARCHAR(12),
        site VARCHAR(10)
        )
        """)
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Muss man eine Transaktion commiten?
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

vielleicht lese ich das auch falsch, allerdings wird hier:
sowas ähnliches gemacht, Transaktion in doppel quotes gesetzt.

Wenn ich

Code: Alles auswählen

with closing(conn.cursor()) as cursor:
    cursor.execute(
        'CREATE TABLE main(zeit DATETIME, prom VARCHAR(8), message VARCHAR(14), did INTEGER, rufnummer VARCHAR(12), site VARCHAR(10))')
verwende gibt es auch keine Tabelle, Fehlermeldung und auch im syslog wird kein zugriff auf die Datenbank registriert.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Mit folgendem Code klappt es auf anhieb.

Code: Alles auswählen

conn = _mssql.connect(
        server='localhost',
        user='sa',
        password='xxx',
        database='F1'
        )

conn.execute_non_query('CREATE TABLE main(zeit DATETIME, prom VARCHAR(8), message VARCHAR(14), did INTEGER, rufnummer VARCHAR(12), site VARCHAR(10))')

Wie kann ich prüfen ob pymssql richtig läuft?
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Die Dokumentation sieht die Verwendung des Contextmanagers wohl etwas anders vor:

Code: Alles auswählen

with pymssql.connect(server, user, password, "tempdb") as conn:
    with conn.cursor(as_dict=True) as cursor:
        ...
Und in dem von dir verlinkten Beispiel gibt es ein commit.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Ok, jetzt verstehe ich, was du mit, "commiten", meinst,......hoffentlich. :-)
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Code: Alles auswählen

cursor = conn.cursor()
cursor.execute('CREATE TABLE main(zeit DATETIME, prom VARCHAR(8), message VARCHAR(14), did INTEGER, rufnummer VARCHAR(12), site VARCHAR(10))')
conn.commit()
conn.close()
so gehts schon mal.

Danke für den Hinweis, Sparrow.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Aktuell ist mein Zwischenstand, dass alle relevanten Einträge in die Tabelle eingetragen werden und leider bei jeder Ausführung erneut.

Das soll doch durch "try and except" und "isinstance()" verhindert werden, oder?
Komme aktuell durch suchen nicht weiter.

Von meinem denken her, würde ich mit einer Abfrage die Zeilen der Tabelle in Strings einlesen und sie dann mit den match lines vergleichen.
Da allerdings täglich ca. 5 bis 8 tausend Einträge in die Datenbank kommen werden, wird das auf dauer sicherlich sehr Hardware intensiv.
Welche Möglichkeiten gibt es noch?

Kann mir das bitte nochmal jemand genauer erläurtern?

Code: Alles auswählen

#!/usr/bin/env python3
#
import os
import sys
import re
from collections import namedtuple
from contextlib import closing
from datetime import datetime as DateTime

import pymssql

LOG_PATH = '/var/log/ESI/ESI2'
LOG_FILENAME_TEMPLATE = os.path.join(LOG_PATH, 'F1log_{}.txt')

COMMON_PATTERN = r'^(\d+) (?P<zeit>\d{2}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) '
MAIN_RE = re.compile(COMMON_PATTERN + r'(?P<prom>\d{8}) (?P<message>.{14}) .+ N00 (?P<did>\d{4}) (?P<rufnummer>\d{8,12})? .+ ?SITE=(?P<site>\d{8,10})')
ERROR_RE = re.compile(COMMON_PATTERN + r'ISDN Fehlanruf C.00 (?P<did>\d{4}) (?P<rufnummer>\d{8,12})? ?Diag:(?P<message>[A-Z ]{14,18})')

Main = namedtuple('Main', 'zeit prom message did rufnummer site')
Error = namedtuple('Error', 'zeit prom message did rufnummer site')

server = 'localhost'
user = 'sa'
password = 'xxx'
database = 'F1'

def create_tables(connection):
    with closing(connection.cursor()) as cursor:
        cursor.execute(
                'CREATE TABLE main(zeit DATETIME, prom VARCHAR(8),'
                'message VARCHAR(18), did INTEGER, rufnummer VARCHAR(12)'
                'site VARCHAR(10))'
                )
    
def get_common(match):
    zeit = DateTime.strptime(match.group('zeit'), '%d/%m/%y %H:%M:%S')
    did = int(match.group('did'))
    rufnummer = match.group('rufnummer')
    return (zeit, did, rufnummer)

def parse_log(lines):
    for line in  lines:
        match = MAIN_RE.match(line)
        if match:
            zeit, did, rufnummer = get_common(match)
            message = match.group('message')
            prom = match.group('prom')
            site = match.group('site')
            yield Main(zeit, prom, message, did, rufnummer, site)
        else:
            match = ERROR_RE.match(line)
            if match:
                zeit, did, rufnummer = get_common(match)
                prom = None 
                site = None
                message = match.group('message')
                yield Error(zeit, prom, message, did, rufnummer, site)

def insert_entries(connection, entries):
    with closing(connection.cursor()) as cursor:
        for entry in entries:
            if isinstance(entry, Main):
                cursor.execute(
                        'INSERT INTO main (zeit, prom, message, did, rufnummer, site)'
                        'VALUES (%s, %s, %s, %s, %s, %s)',
                        entry
                        )
            elif isinstance(entry, Error):
                cursor.execute(
                        'INSERT INTO main (zeit, prom, message, did, rufnummer, site)'
                        'VALUES (%s, %s, %s, %s, %s, %s)',
                        entry
                        )

def main():
    log_filename = LOG_FILENAME_TEMPLATE.format(sys.argv[1])
    with open (log_filename) as lines:
        connection = pymssql.connect(
                server, user, password, database
                )
        with closing(connection):
            try:
                insert_entries(connection, parse_log(lines))
            except:
                connection.rollback()
                raise
            else:
                connection.commit()

main()
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Warum sollte isinstance das verhindern? isinstance dient der Unterscheidung in welcher Tabelle ein Eintrag gemacht wird. Da isinstance aber keine Eigenschaften der Objekte beruecksichtigt jenseits ihrer Klasse, verhindert es auch keine Mehrfachanlage. Und warum sollte das try/except daran etwas aendern? Das wuerde - wenn es greift - den *GESAMTEN* Prozess des eintragens von Daten rueckgaengig machen. Ebenfalls nicht pro Eintrag irgendetwas pruefen.

Deine Tabellen enthalten keine Kriterien, nach denen die Eindeutigkeit eines Eintrags bestimmt wird. Also meckert die Datenbank auch nicht, wenn es zu einem Doppeleintrag kommt. Wodurch bestimmt sich fuer dich, ob ein Eintrag schonmal vorgekommen ist?
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Deine Tabellen enthalten keine Kriterien, nach denen die Eindeutigkeit eines Eintrags bestimmt wird. Also meckert die Datenbank auch nicht, wenn es zu einem Doppeleintrag kommt. Wodurch bestimmt sich fuer dich, ob ein Eintrag schonmal vorgekommen ist?
Wenn exakt die gleiche zeile schon in der Datenbank vorkommt, also jede Zelle der Zeile schon mal eingetragen wurde.
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dann musst du das so definieren, indem du einen UNIQUE-Constraint einfuehrst, der alle Spalten umfasst. Danach solltest du zumindest einen Fehler bekommen, wenn du versuchst, die gleiche Zeile zweimal anzulegen. Und dann geht's weiter, du musst dein Skript so umschreiben, dass es entweder alles zeilenweise einfueght, committet oder zurueckrollt, oder du versuchst dich an Mechanismen wie sie hier https://stackoverflow.com/questions/209 ... -not-exist beschrieben werden.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Also müsste man jeder Zeile das Attribut, "unique" geben, damit es eine Fehlermeldung gibt, auf grund derer man dann die eintragung dieser Zeile skippen kann?
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@fredvonfat: nein. Du mußt alle Felder zusammen einen UNIQUE-Constraint geben, weil nur alle Felder zusammen eindeutig sein müssen.

Inzwischen hast Du es ja geschafft, dass Fehlerzeilen und normale Zeilen das gleiche Schema teilen, dann ist aber eine Unterscheidung zwischen Error und Main unsinnig.

Wenn man garantieren kann, dass nachträglich keine weiteren Einträge hinzukommen, wäre das einfachste Kriterium das Datum, so dass man alle Einträge skippen kann, die älter sind als der neuste Eintrag in der Datenbank.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

nzwischen hast Du es ja geschafft, dass Fehlerzeilen und normale Zeilen das gleiche Schema teilen, dann ist aber eine Unterscheidung zwischen Error und Main unsinnig.
Ja, daran hatte ich auch schon gedacht.

Würde es reichen, wenn ich "def insert_entries" anpasse? In "def parse_log" benötige ich die Unterscheidung ja noch.
Wenn man garantieren kann, dass nachträglich keine weiteren Einträge hinzukommen, wäre das einfachste Kriterium das Datum, so dass man alle Einträge skippen kann, die älter sind als der neuste Eintrag in der Datenbank.
Leider reicht das nicht aus, da der Zeitstempel durchaus gleich sein kann, allerdings ist dann mindestens eines der anderen Felder anders.

Auszuschließen ist, dass Zeit und Rufnummer bei zwei aufeinander folgenden Einträgen gleich ist.
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dann eben der UNIQUE-constraint ueber die notwendigen Spalten. https://stackoverflow.com/questions/347 ... le-columns
Antworten