Logfile in eine MSSQL Datenbank einlesen

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Ich such irgendwie ne Lösung, so dass nicht jedes mal die komplette Tabelle untersucht werden muss. Da können im Laufe der Zeit viele hundert tausende Zeilen drin sein.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Eigentlich muss ich nur die Einträge vom gleichen Tag vergleichen, denn am nächsten tag ist es wieder ein neues logfile.....
Benutzeravatar
sparrow
User
Beiträge: 4165
Registriert: Freitag 17. April 2009, 10:28

Ich bin ja für praktische Lösungen.
Ich würde mir nach der erfolgreichen Speicherung der Daten merken, wieviele Zeilen ich aus der Log-Datei gelesen wurden, und diese beim nächsten Mal skippen.

Das mit den Constraints würde ich aber trotzdem machen (auch wenn das teuer ist) um am kleinsten Punkt ausschließen zu können, dass eine Zeile doppelt hinein rutscht.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Constraints hab ich schon über das SQL Management Studio eingerichtet. Das funktioniert schon.
Das mit den Zeilen merken erscheint mir eine gute Idee, die steht ja mit im logfile.
Habe nur noch keine Idee, wie das merken funktioniert.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Man könnte sie in eine Datenbank schreiben und dann nachschauen, ob die Zeilen schon drin sind 🤔🤔🤔

Ernsthaft: Datenbanken sind für sowas gemacht. Pragmatisch ist nicht sich irgendwelchen kruden Kriterien auszudenken, nach denen man da voreilig optimiert. Sondern die DB ihren Job machen zu lassen. Der unique constraint sollte bei voller bei voller Abfrage eigentlich auch einen Index enthalten, so das die Antwort ob die Zeile enthalten ist effizient berechnet wird.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Danke für die super Anregungen.
Jetzt habe ich wieder viele Hausaufgaben.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

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,
Nach einmal drüber schlafen und grübeln, erscheint mir der Vorschlag von deets am einfachsten realisierbar.

Jedoch fiel mir dabei auf, das ich noch einige verständnisprobleme zu den funktionen habe.

main:
Hier wird "insert_entries" einmalig aufgerufen und die objekte von "parse_log" als parameter übergeben?
Ich komme darauf, weil nach der ersten Zeile gleich ein Abbruch erfolgt wenn die Eintragung dieser ein Fehler verursacht.
Oder wird try so häufig ausgeführt, bis keine Daten mehr von "parse_log" kommen und so lange nichts unerwartetes auftritt?

insert_entries:
Wo wird der Parameter "entries" definiert oder ist dieser direkt von Python gegeben?
Wenn zum start von "insert_entries", alle Objekte von "parse_log" zur Verfügung stehen, scheint diese Funktion, die Menge der Objekte zeilenweise abzuarbeiten und in die Datenbank zu übertragen.
Oder wird auch "insert_entries" so oft aufgerufen, wie erforderlich und bekommt die Objekte zeilenweise serviert?

parse_log :
1. Hier werden die Zeilen des Logfiles verarbeitet, manipuliert und gesammelt in einem Tupel bereitgestellt?
2. Oder geschieht die Bereitstellung zeilenweise und die funktion wird so oft aufgerufen wie notwendig?
Was ich hier im Forum schon gelernt habe ist, dass man eine Funktion dann definiert, wen man den entsprechenden Aufruf häufiger verwendet.
Demnach würde ich hier zu 2. tendieren. Liege ich da richtig?

Wenn "insert_entries" und "parse_log" zeilenweise liefern, dann müsste ich demnach nur in "main" eine "for-Schleife" um "try / except / else" legen und dann sollte der Vorschlag von deets realisiert sein, oder?
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

All diese Fragen sind wirklich absolute Grundlagen von Python. Hast du schon mal das Tutorial durchgearbeitet? Zu den Kontrollstrukturen if/for/while/try und der Definition von Funktionen und deren Aufruf steht da wirklich alles episch erklaert. Es bringt nichts, wenn du beliebig Annahmen ueber die Art und Weise wie ein Programm funktioniert triffst, und wir die dann einzeln korrigieren muessen. Und wenn du gelernt hast, wie man Funktionen erstellt und aufruft, dann frage ich mich, warum du dich fragst, woher "entries" kommt.

Das einzige, das ein bisschen fortgeschrittener ist, ist parse_log. Das ist dir aber auch schon erklaert worden, dass es sich dabei um einen generator handelt.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Ja, ich bin mir bewusst, das ich gravierende Lücken in den Grundlagen zu Python habe.
Kapitelweise hab ich mir ein paar tutorials angeschaut.
Zum Verständnis dessen hilft es mir, wenn ich anhand eines Projektes, antworten auf die entstandenen Fragen bekomme, auch wenn es nur ein ja oder nein ist, oder ein Hinweis auf Quellen für Informationen.
Manchmal stehe ich einfach auf dem Schlauch und brauche nen kleinen Schubs.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Tut mir leid, aber das geht weit ueber das Ausmass von "schubsen" hinaus, die ich hier leisten kann. Vielleicht haben andere da mehr Zeit. Viele deiner Fragen wurden uebrigens schon beantwortet, und auch Quellen genannt. Das nur am Rande.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Danke für deine Zeit, ich werde mir das geschriebene nochmal intensiver anschauen.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

@__deets__
Nochmals Danke für den Schubs.
Habe die relevanten Beiträge und Tutorials nochmal durchgearbeitet. Die Fragen haben sich dadurch erübrigt.
Es läuft jetzt.

Danke nochmal an alle, die hier mitgeholfen haben.

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:
            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):
            for line in lines:
                try:
                    insert_entries(connection, parse_log(lines))
                except pymssql.IntegrityError:
                    pass
                except:
                    connection.rollback()
                    raise
                else:
                    connection.commit()

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

@fredvonfat: Das läuft ziemlich sicher so nicht. Das wird nichts in die Datenbank eintragen. Weil keiner der beiden regulären Ausdrücke auf 1 Zeichen matcht, egal welches.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Das verstehe ich nicht, ich sehe doch wie die Werte in der Datenbank landen.
Kannst du das genauer erläutern?
Benutzeravatar
__blackjack__
User
Beiträge: 13007
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@fredvonfat: Okay, mein Fehler, ich habe übersehen das die ``for``-Schleife in der Hauptfunktion auf eine andere Art sinnlos ist als ich vermutete. Ich dachte Du machst da auch tatsächlich etwas mit der Laufvariablen, aber die Schleife ist einfach nur total sinnfrei.

Es macht letztlich aber auch nicht so wirklich was Du willst, denn sobald eine Zeile schon vorhanden ist, werden folgende nicht mehr berücksichtigt. Vielleicht ist das sogar das was Du willst, aber der Code sieht halt extrem nach etwas anderem aus, was er aber nicht macht.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

@__blackjack__: woran machst du fest, dass die anderen nicht beruecksichtigt werden? Es gibt doch immer einen neuen Cursor. Das so zu machen ist natuerlich urst langsam, aber semantisch sieht's korrekt aus.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Es macht letztlich aber auch nicht so wirklich was Du willst, denn sobald eine Zeile schon vorhanden ist, werden folgende nicht mehr berücksichtigt. Vielleicht ist das sogar das was Du willst, aber der Code sieht halt extrem nach etwas anderem aus, was er aber nicht macht.
Ohne die for-Schleife in main(), hatte ich ganau den Effekt, den du beschreibst. Erst seit dem ich die For-Scheife dort eingefügt habe, passiert genau das was ich möchte. Zumindest scheint es so. Ich überprüfe das mit dem Management Studio und schaue mir die Zeitstempel an.

Das logfile, welches ich auswerte, wächst minütlich um ca 10 bis 50 Einträge. Wenn ich den code ausführe kann ich erkennen, dass auch die aktuellen Einträge im Log landen, ohne dass das Programm abbricht.

Sicherlich geht das eleganter und mich interessieren auch mögliche Fallstricke, aber deine eben getroffene Aussage deckt sich nicht mit dem Effekt, den ich beobachte.
Benutzeravatar
__blackjack__
User
Beiträge: 13007
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@__deets__: Okay, ich sollte bei der Wärme nix mehr posten. Semantisch sieht das total falsch aus! Wie gesagt ich bin jetzt massivst verunsichert, aber ich bin auch jetzt immer noch der Meinung das mindestens jeweils der nächste Logeintrag nach einem `pymssql.IntegrityError` nicht berücksichtigt wird, sowie auch ganz am Anfang auch schon die allererste Zeile des Logs nicht berücksichtigt wird, weil die zwei ``for``-Schleifen über die `lines` semantisch IMHO schlicht falsch sind und eben auch zu Fehlern führen werden. Falls nicht, hat man entweder Glück oder es tatsächlich so, dass man an den entsprechenden Stellen Zeilen im Log komplett ignorieren darf. Aber selbst wenn man das dürfte, würde ich das nicht Dokumentieren, sondern den Code ändern, so dass einen das nicht mehr so verwirrt.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ah, jetzt sehe ich auch erst was du meinst. Das ist definitiv krude und fehlerhaft. Aber eben nicht komplett, es laufen schon Zeilen ein.

@fredvonfat: du musst das umstellen. Die einfachste Loesung ist IMHO insert_entries umzuschreiben zu insert_entry, also nur EINEN Eintrag zu machen. Und das gleiche gilt fuer parse_log, das darf dann nur EINE Zeile verarbeiten. Um da noch mal den nachdruecklichen Schubs zu geben: beide Funktionen duerfen NICHT mehr eigene for-Schleifen enthalten! Denn die for-schleife hast du jetzt aussen drum rum. Das ist auch ok fuer den gewuenschten Zweck, aber so konsumierst du einen Iterator (die Datei) an zwei stellen, und dadurch verlierst du Information.

Nachtrag: und parse_log ist dann auch kein Generator mehr! Sondern eine normale Funktion mit return.
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

@__deets__: statt alles umzuschreiben, müßte man nur parse_log in die äußerste Schleife verschieben und die innere entfernen.

Code: Alles auswählen

def parse_log(lines):
    for line in lines:
        match = MAIN_RE.match(line) or ERROR_RE.match(line) # if first regex doesn't match, try next
        if match:
            groups = match.groupdict()
            zeit, did, rufnummer = get_common(match)
            message = groups.get('message')
            prom = groups.get('prom')
            site = groups.get('site')
            yield Main(zeit, prom, message, did, rufnummer, site)

def insert_entry(connection, entry):
    with closing(connection.cursor()) as cursor:
        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):
            for entry in parse_log(lines):
                try:
                    insert_entry(connection, entry)
                except pymssql.IntegrityError:
                    pass
                except:
                    connection.rollback()
                    raise
                else:
                    connection.commit()

if __name__ == '__main__':
    main()
Antworten