Forum: PC-Programmierung Python: Apache Log Einlesen und Filtern


von PythonBob (Gast)


Lesenswert?

Hey,

ich brauche einmal eure Hilfe.

Ich muss ein Apachelog einlesen und als Ausgabe brauche ich die Anzahl 
der 200er, 300er, 400er und 500er Meldungen.
Ich habe schon Stunden damit verbracht aber mir fehlen Regex Kenntnisse.
Danke für eure Hilfe.


Beispiel Ausgabe:

Fehlercodes
~~~~~~~~~~~
200: 21344
300: 14353
400: 864
500: 5

Die Gesamten Zeilen des Logs sind wie folgt aufgebaut.:
1
172.16.11.12 - - [26/May/2013:14:06:05 +0200] "GET /navleiste/i_xyz_navleiste.gif HTTP/1.1" 200 1348

von Εrnst B. (ernst)


Lesenswert?

In etwa, ganz ohne Regex, ohne python nur mit Shell-"Hausmitteln":
1
cut -d '"' -f 3  access.log | cut -d ' ' -f 2 | sort -n | uniq -c


ggfs quick&dirty aus python heraus "bash -c '...'" ausführen lassen...

: Bearbeitet durch User
von Stephan G. (Firma: privat) (morob)


Lesenswert?

Εrnst B. schrieb:
> In etwa:
>
1
> cut -d '"' -f 3  access.log|cut -d ' ' -f 2 | sort -n | uniq -c
2
>
>
>
> ggfs quick&dirty aus python heraus "bash -c '...'" ausführen lassen...

so ungefähr war auch mein gedanke

: Bearbeitet durch User
von imonbln (Gast)


Lesenswert?

Der folgende Code sollte das machen, bitte beachten, er ist nicht durch 
getestet und etwas arm an Fehlerbehandlung.

1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
4
from collections import Counter
5
6
def statuscnt(fname):
7
    result = Counter()
8
    with open(fname) as fin:
9
        for line in fin:
10
            code = line.split()[-2]
11
            status[code] += 1
12
13
    # Ausgabe
14
    print('Fehlercodes')
15
    for status, count in sorted(result.items()):
16
        print('{0}: {1}'.format(status, count)

der Haupttrick hier ist code = line.split()[-2] welches die logzeile des 
Access log nimmt und bei den Leerzeichen in eine Liste teilt
Weiter geht die Zeile davon aus dass, das vorletzte element der Liste 
(-2) der Statuscode ist. Hier solltest du ggf. noch eine Plausibilität 
Prüfung für die Annahme machen (z.b.) Probieren ob int(code) eine 
Execption wirft.

Der zweite nette Trick ist die Counter-Klasse zu verwenden welche bei 
status[code] eine 0 liefert wenn Code noch nicht bekannt ist. deshalb
geht status[code] += 1 gut selbst wenn das noch nicht definiert ist.

von imonbln (Gast)


Lesenswert?

imonbln schrieb:
> status[code] += 1

das muss naturlich result[code] +=1 sein.

von PythonBob (Gast)


Lesenswert?

Danke für deine Hilfe. :)

Ich bekomme nur leider folgende Meldung.:
1
SyntaxError: unexpected EOF while parsing

von imonbln (Gast)


Lesenswert?

PythonBob schrieb:
> Danke für deine Hilfe. :)
>
> Ich bekomme nur leider folgende Meldung.:
> SyntaxError: unexpected EOF while parsing

eine Zeilennummer wäre toll :)

>   print('{0}: {1}'.format(status, count)

hier fehlt eine schließende Klammer

print('{0}: {1}'.format(status, count))


hoffe das war es.

von PythonBob (Gast)


Angehängte Dateien:

Lesenswert?

Ja das war es: ^^ Bekomme aber leider trz. kein ergebnis. (Villt. liegt 
es auch einfach an mir bin ein Python Noob)

Ich habe mein ein Screenshot angehangen.

von Horst (Gast)


Lesenswert?

Du musst die Funktion schon aufrufen...

von Imonbln (Gast)


Lesenswert?

Das Open wird schon in der Funktion gemacht also Datei ist nur der path 
ohne open und dann die Funktion aufrufen.
1
def statuscnt(Datei):
2
[....]
3
4
Datei = 'C:\\Users\\XXXX\\Destop\\apache.log' 
5
statuscnt(Datei)

von PythonBob (Gast)


Lesenswert?

Ja vielen Dank....

Jetzt habe auch ich es verstanden haha
Es funkionert alles. :D

von PythonBob (Gast)


Lesenswert?

@imonbln

Nochmal Danke aber ich hab enoch eben eine Frage.:

Wie funktioniert das ganze denn wenn ich mehrere Logs einlesen möchte 
und die Statistik für diese zusammen erstellen will?

von imonbln (Gast)


Lesenswert?

PythonBob schrieb:
> Nochmal Danke aber ich hab enoch eben eine Frage.:
>
> Wie funktioniert das ganze denn wenn ich mehrere Logs einlesen möchte
> und die Statistik für diese zusammen erstellen will?

Ich werde nicht deine Hausaufgabe machen :)

hier ein paar Hinweise.

1.) du kannst die Funktion pro Logfile Aufrufen
2.) die Funktion sollte vielleicht nicht selbst die Ausgabe machen 
sondern das result Objekt zurück geben.
3.) Das result Objekt (Counter Klasse) hat eine update-Methode, welche 
als Parameter eine andere Instanz der Counter Klasse akzeptiert und die 
Addition pro Eintrag für dich macht.

Mit den Hinweisen solltest du das Programm ändern können. Wenn du 
Probleme hast oder Hilfe brauchst kannst du gerne wieder fragen aber 
erstmals müssen nun ein paar Zeilen Code von dir kommen.

von PythonBob (Gast)


Lesenswert?

@imonbln

Ich will ja auch nicht, dass du hier alles für mich schreibst, solche 
Tipps sind völlig ausreichend.
Und du machst da auch nicht meine Hausaufgaben oder so weil das nur ein 
kleiner Teil von einem größerem Projekt ist. ^^


Ich habe versucht deine Tipps umzusetzen und bin schon etwas weiter 
gekommen.
Die Ausgabe sieht es wie folgt aus.:
1
200: Counter({'200': 43230, '304': 27530, '401': 974, '303': 720, '404': 630, '302': 484, '206': 202, '406': 60, '207': 54, '301': 32, '403': 16, '405': 14, '501': 14, '500': 10, '204': 2})
2
204: Counter({'200': 43230, '304': 27530, '401': 974, '303': 720, '404': 630, '302': 484, '206': 202, '406': 60, '207': 54, '301': 32, '403': 16, '405': 14, '501': 14, '500': 10, '204': 2})

Also ich habe es quasi geschaft Zwei Log Dateien geminsam auszuweiten 
und die Fehleroces zusammenzurechnen aber die Ausgabe ist jetzt halt in 
einem recht komischen Format.

Das ist mein Code
1
from collections import Counter
2
3
4
def statuscnt(DateiA):
5
    result1 = Counter()
6
    with open(DateiA) as fin:
7
        for line in fin:
8
            code = line.split()[-2]
9
            result1[code] += 1
10
11
    result2 = Counter()
12
    with open(DateiB) as fin:
13
        for line in fin:
14
            code = line.split()[-2]  #
15
            result2[code] += 1
16
    for status, count in sorted(result2.items()):
17
        sum = result1 + result2
18
        print('{0}: {1}'.format(status, sum))
19
20
DateiA = 'C://Users//XXX//Desktop//apache.log'
21
DateiB = 'C://Users//XXX//Desktop//apache.log'
22
statuscnt(DateiA)

Wahrscheinlich ist der Fehler super offensichtlich aber ich habe es 
nicht besser hibekommen. :P

von imonbln (Gast)


Lesenswert?

PythonBob schrieb:
> Ich habe versucht deine Tipps umzusetzen und bin schon etwas weiter
> gekommen.

und nicht einen davon in dein Code eingebaut ;)

PythonBob schrieb:
> result2 = Counter()
>     with open(DateiB) as fin:
>         for line in fin:
>             code = line.split()[-2]  #

das open klappt nur weil DateiB eine globale Variable ist. hier hätte 
ich erwartetet das du statt dessen zweimal die funktion statuscnt 
aufrufst einmal mit DateiA und einmal mit DateiB. Dein Copy&Paste Ansatz 
wird bei 20 Dateien Stressig.
1
 
2
def statuscnt(DateiA):
3
    result1 = Counter()
4
    with open(DateiA) as fin:
5
        for line in fin:
6
            code = line.split()[-2]
7
            result1[code] += 1
8
    return result

wäre mein Vorschlag für die Funktion.

PythonBob schrieb:
> sum = result1 + result2
>         print('{0}: {1}'.format(status, sum))

das ist wie du schon bemerkt hast nicht das was du willst, hier wäre 
Hinweis 3 dein Freund.
1
 gesamt = Counter()
2
 [ für jede datei ]
3
   gesamt.update(teilergebnis) 
4
 # Ausgabe von gesamt

von Sheeva P. (sheevaplug)


Lesenswert?

PythonBob schrieb:
> Ich habe versucht deine Tipps umzusetzen und bin schon etwas weiter
> gekommen.

Die Idee ist, den Code zu verstehen. Vielleicht magst Du die 
Dokumentation von collections.Counter und und str.split lesen?

> Wahrscheinlich ist der Fehler super offensichtlich aber ich habe es nicht
> besser hibekommen. :P

Mehr als offensichtlich... ;-)

Tipp 0: statuscnt() kann die Counter-Instanz zurückgeben.
Tipp 1: Counter-Instanzen kann man einfach addieren (_add_).
Tipp 2: argparse.ArgumentParser benutzen:
1
parser = ArgumentParser(description='Parse Apache Logs')
2
parser.add_argument('filenames', nargs='+', help='Logs')
3
args = parser.parse_args()
4
for filename in args.filenames:
5
    ...

HTH,
Sheeva

von PythonBob (Gast)


Lesenswert?

@imonbln
@Sheeva Plug


Sooooo ich habe das ganze jetzt wie folgt gelöst.: (ist aber auf zwei 
Dateien beschränkt aber erstmal ausreichend)

Weiter oben in meinem Code ist eine Abfrage ob man eine weitere Datei 
einlesen möchte und  "user_input_3" ist dann der Dateipfad.

1
result = Counter()
2
    while True:
3
#Für eine Datei
4
        if user_input_2 == 'Nein':
5
            def statuscnt(DateiA, result):
6
             ...
7
            DateiA = user_input
8
            statuscnt(DateiA, result)
9
            break
10
11
#Für zwei Dateien
12
        elif user_input_2 == 'Ja':
13
            def statuscnt2(DateiA, DateiB, result):
14
               with open(DateiA) as fin:
15
                ...
16
               result.update(result)  
17
               for status, count in sorted(result.items()):
18
                   print('{0}: {1}'.format(status, count)) 
19
20
21
            DateiA = user_input
22
            DateiB = user_input_3
23
            statuscnt2(DateiA, DateiB, result)
24
            break
Das funktionert soweit ohne Probleme
Nochmal Danke für eure Unterstützung :)

von Imonbln (Gast)


Lesenswert?

PythonBob schrieb:
> if user_input_2 == 'Nein':
>             def statuscnt(DateiA, result):
>              ...

Das ist gefährlich innerhalb einer if Bedingung eine Funktion zu 
deklarieren. Sowas sollte man nicht machen. Das fällt dir auf die Füße.

Ausserdem verstehe ich nicht warum du für denn fall eine Datei eine 
andere Funktion brauchst als für den fall von mehren Dateien.

Die Übergabe des result Parameter an deine Funktion erschließt sich mir 
nicht. Ich denke das ist überflüssig aber vielleicht verbirgt sich in 
dein [...] eine interessante Logik dazu

PythonBob schrieb:
> statuscnt(DateiA, result)
>             break

Für beide falle kannst du die gleiche Funktion verwenden.

PythonBob schrieb:
> Sooooo ich habe das ganze jetzt wie folgt gelöst.: (ist aber auf zwei
> Dateien beschränkt aber erstmal ausreichend)

Nein, mit der Funktion wie ich sie Dir in im letzten Post geschrieben 
habe mit dem Tipp2 von Sheeva Plug kannst du n-Dateien auf einmal 
verarbeiten.

hier nochmal einen Tipp für n-Dateien.
1
gesamt = Counter()
2
for filename in args.filenames:
3
    teilerebnis = statuscnt(filename)
4
    gesamt += teilergbnis
5
#Ausgabe


Generell habe ich gegenwärtig nicht den Eindruck das du spezielle Python 
Probleme hast, ich denke dir fehlt eher noch etwas Programmiererfahrung. 
Ich würde dir zum Beispiel empfehlen, Dir nochmal klar zu machen was 
Funktionen sind und wie man diese Benutzt (ob in Python oder was anderes 
spielt keine Rolle).

Ansonsten wird das schwer, dir helfen zu können, denn für mich (und 
sicher auch andere) steht hier im Thread seit gestern (mit den Tipps von 
Sheeva Plug und mir) das Komplette Programm welches du schreiben willst.
Du musst eigentlich nur noch (TM) die Puzzelteile verbinden.
Natürlich ist das für ein Anfänger schwer, aber aufgrund deiner 
scheinbar geringen Programmiererfahrung, ist es auch nicht gerade leicht 
dir zu helfen ohne dir die Lösung hinzuschreiben.
Daher mein Tipp nimm dir nochmal dein Programm vom 07.04 welches eine 
Datei  Parsen kann, gehe den Thread nochmal durch und erweitere es in 
dieser Reihenfolge

1.) schreibe die funktion statuscnt so um das sie mit return das Counter 
Objekt zurück gibt.

2.) ändere den Aufruf der Funktion statuscnt so das dieser den return 
wert von statuscnt speichert.

3.) nimm den return wert von statuscnt (Tipp die Variable von 2.) und 
gebe es aus (tipp die Ausgabe macht jetzt, Stand 07.04 noch die Funktion 
statuscnt)

4.) Überlege wie du nun mehr als eine Datei ausgeben kannst ( Tipp die 
Funktion statuscnt muss spätestens nach 3. nicht mehr geändert werden)

HTH

imonbln

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.