Ziehen Sie Java-Fehler-Stacks aus Protokolldateien heraus

Ich habe eine Java-Anwendung, die beim Ausfall einen Fehlerstapel ähnlich dem unten für jeden Fehler schreibt.

<Errors> <Error ErrorCode="Code" ErrorDescription="Description" ErrorInfo="" ErrorId="ID"> <Attribute Name="ErrorCode" Value="Code"/> <Attribute Name="ErrorDescription" Value="Description"/> <Attribute Name="Key" Value="Key"/> <Attribute Name="Number" Value="Number"/> <Attribute Name="ErrorId" Value="ID"/> <Attribute Name="UserId" Value="User"/> <Attribute Name="ProgId" Value="Prog"/> <Stack>typical Java stack</Stack> </Error> <Error> Similar info to the above </Error> </Errors> 

Ich schrieb einen Java-Log-Parser, um durch die Log-Dateien zu gehen und Informationen über solche Fehler zu sammeln und während es funktioniert, ist es langsam und ineffizient, vor allem für Log-Dateien in den Hunderten von Megabyte. Ich benutze nur grundsätzlich String Manipulation zu erkennen, wo die Start / End-Tags sind und tally sie auf.

Gibt es einen Weg (entweder über Unix grep, Python oder Java), um die Fehler effizient zu extrahieren und die Anzahl der Male zu erhalten, die jeder passiert? Die gesamte Protokolldatei ist kein XML, also kann ich keinen XML-Parser oder Xpath verwenden. Ein weiteres Problem, dem ich vorangehe, ist, dass manchmal das Ende eines Fehlers in eine andere Datei rollen könnte, so dass die aktuelle Datei möglicherweise nicht den gesamten Stapel wie oben hat.

BEARBEITUNG 1:

Hier ist was ich derzeit habe (relevante Teile nur Platz sparen).

 //Parse files for (File f : allFiles) { System.out.println("Parsing: " + f.getAbsolutePath()); BufferedReader br = new BufferedReader(new FileReader(f)); String line = ""; String fullErrorStack = ""; while ((line = br.readLine()) != null) { if (line.contains("<Errors>")) { fullErrorStack = line; while (!line.contains("</Errors>")) { line = br.readLine(); try { fullErrorStack = fullErrorStack + line.trim() + " "; } catch (NullPointerException e) { //End of file but end of error stack is in another file. fullErrorStack = fullErrorStack + "</Stack></Error></Errors> "; break; } } String errorCode = fullErrorStack.substring(fullErrorStack.indexOf("ErrorCode=\"") + "ErrorCode=\"".length(), fullErrorStack.indexOf("\" ", fullErrorStack.indexOf("ErrorCode=\""))); String errorDescription = fullErrorStack.substring(fullErrorStack.indexOf("ErrorDescription=\"") + "ErrorDescription=\"".length(), fullErrorStack.indexOf("\" ", fullErrorStack.indexOf("ErrorDescription=\""))); String errorStack = fullErrorStack.substring(fullErrorStack.indexOf("<Stack>") + "<Stack>".length(), fullErrorStack.indexOf("</Stack>", fullErrorStack.indexOf("<Stack>"))); apiErrors.add(f.getAbsolutePath() + splitter + errorCode + ": " + errorDescription + splitter + errorStack.trim()); fullErrorStack = ""; } } } Set<String> uniqueApiErrors = new HashSet<String>(apiErrors); for (String uniqueApiError : uniqueApiErrors) { apiErrorsUnique.add(uniqueApiError + splitter + Collections.frequency(apiErrors, uniqueApiError)); } Collections.sort(apiErrorsUnique); 

EDIT 2:

Entschuldigung zum Vergessen, die gewünschte Ausgabe zu erwähnen. Etwas wie das unten wäre ideal.

Count, ErrorCode, ErrorDescription, Liste der Dateien, die es in (wenn möglich)

3 Solutions collect form web for “Ziehen Sie Java-Fehler-Stacks aus Protokolldateien heraus”

Angesichts Ihrer aktualisierten Frage:

 $ cat tst.awk BEGIN{ OFS="," } match($0,/\s+*<Error ErrorCode="([^"]+)" ErrorDescription="([^"]+)".*/,a) { code = a[1] desc[code] = a[2] count[code]++ files[code][FILENAME] } END { print "Count", "ErrorCode", "ErrorDescription", "List of files it occurs in" for (code in desc) { fnames = "" for (fname in files[code]) { fnames = (fnames ? fnames " " : "") fname } print count[code], code, desc[code], fnames } } $ $ awk -f tst.awk file Count,ErrorCode,ErrorDescription,List of files it occurs in 1,Code,Description,file 

Es braucht noch gawk 4. * für die 3. arg zu passen () und 2D Arrays aber wieder das ist leicht in jedem awk gearbeitet.

Pro Antrag in den Kommentaren hier ist eine Nicht-Gawk-Version:

 $ cat tst.awk BEGIN{ OFS="," } /[[:space:]]+*<Error / { split("",n2v) while ( match($0,/[^[:space:]]+="[^"]+/) ) { name = value = substr($0,RSTART,RLENGTH) sub(/=.*/,"",name) sub(/^[^=]+="/,"",value) $0 = substr($0,RSTART+RLENGTH) n2v[name] = value } code = n2v["ErrorCode"] desc[code] = n2v["ErrorDescription"] count[code]++ if (!seen[code,FILENAME]++) { fnames[code] = (code in fnames ? fnames[code] " " : "") FILENAME } } END { print "Count", "ErrorCode", "ErrorDescription", "List of files it occurs in" for (code in desc) { print count[code], code, desc[code], fnames[code] } } $ $ awk -f tst.awk file Count,ErrorCode,ErrorDescription,List of files it occurs in 1,Code,Description,file 

Es gibt verschiedene Möglichkeiten, die oben getan werden können, einige kürzer, aber wenn die Eingabe enthält Namen = Wert Paare Ich mag ein name2value Array ( n2v[] ist der Name, den ich normalerweise geben), so kann ich auf die Werte mit ihren Namen zugreifen. Macht den Code einfach zu verstehen und zu modifizieren in Zukunft, um Felder hinzuzufügen, etc.


Hier ist meine vorherige Antwort, da es einige Dinge gibt, die du in anderen Situationen benutzen wirst:

Sie sagen nicht, was Sie wollen, dass die Ausgabe aussieht und Ihre gebuchte Sample-Eingabe ist nicht wirklich ausreichend, um zu testen und zeigen nützliche Ausgabe, aber diese GNU awk Skript zeigt den Weg, um eine Zählung von beliebigen Attribut Namen / Wert Paare zu bekommen du magst:

 $ cat tst.awk match($0,/\s+*<Attribute Name="([^"]+)" Value="([^"]+)".*/,a) { count[a[1]][a[2]]++ } END { print "\nIf you just want to see the count of all error codes:" name = "ErrorCode" for (value in count[name]) { print name, value, count[name][value] } print "\nOr if theres a few specific attributes you care about:" split("ErrorId ErrorCode",names,/ /) for (i=1; i in names; i++) { name = names[i] for (value in count[name]) { print name, value, count[name][value] } } print "\nOr if you want to see the count of all values for all attributes:" for (name in count) { for (value in count[name]) { print name, value, count[name][value] } } } 

.

 $ gawk -f tst.awk file If you just want to see the count of all error codes: ErrorCode Code 1 Or if theres a few specific attributes you care about: ErrorId ID 1 ErrorCode Code 1 Or if you want to see the count of all values for all attributes: ErrorId ID 1 ErrorDescription Description 1 ErrorCode Code 1 Number Number 1 ProgId Prog 1 UserId User 1 Key Key 1 

Wenn Sie Daten über mehrere Dateien verteilt haben, könnte die oben nicht mehr kümmern, nur Liste sie alle auf der Kommandozeile:

 gawk -f tst.awk file1 file2 file3 ... 

Es nutzt GNU awk 4. * für echte multidimensionale Arrays, aber es gibt triviale Workarounds für jede andere awk, wenn nötig.

Ein Weg, um einen awk Befehl auf Dateien rekursiv unter einem Verzeichnis zu führen:

 awk -f tst.awk $(find dir -type f -print) 

Nun, es ist nicht technisch grep , aber wenn du offen bist, andere Standard-UNIX-Esque-Befehle zu verwenden, hier ist ein Ein-Liner, der den Job machen könnte, und es sollte schnell sein (wäre interessiert, Ergebnisse auf deinem Dataset zu sehen ):

 sed -r -e '/Errors/,/<\/Errors>/!d' *.log -ne 's/.*<Error\s+ErrorCode="([^"]*)"\s+ErrorDescription="([^"]*)".*$/\1: \2/p' | sort | uniq -c | sort -nr 

Angenommen, sie sind in Datum Ordnung, die *.log wird auch das Problem der Protokolle rollen (anpassen, um Ihre Log-Namensgebung, natürlich) zu lösen.

Probenausgabe

Aus meinen (dubiosen) Testdaten basiert auf deinem:

  10 SomeOtherCode: This extended description 4 Code: Description 3 ReallyBadCode: Disaster Description 

Kurze Erklärung

  1. Verwenden Sie sed , um nur zwischen ausgewählten Adressen zu drucken (Zeilen, hier)
  2. Verwenden Sie sed wieder, um diese mit einer Regex zu filtern, indem Sie die Kopfzeile durch eine zusammengesetzte, eindeutig genügend fehlerhafte Zeichenfolgen (einschließlich Beschreibung) ersetzen, ähnlich Ihrem Java (oder zumindest was wir davon sehen können)
  3. Sortieren und zählen diese Zeichenfolgen
  4. In absteigender Reihenfolge der Häufigkeit präsentieren

Ich nehme an, dass, da Sie Unix grep erwähnen, können Sie wahrscheinlich auch perl haben. Hier ist eine einfache perl-lösung:

 #!/usr/bin/perl my %countForErrorCode; while (<>) { /<Error ErrorCode="([^"]*)"/ && $countForErrorCode{$1}++ } foreach my $e (keys %countForErrorCode) { print "$countForErrorCode{$e} $e\n" } 

Angenommen, Sie laufen * nix, speichern Sie dieses Perl-Skript, machen Sie es ausführbar und laufen mit Befehl wie …

 $ ./grepError.pl *.log 

Du solltest Ausgabe wie …

 8 Code1 203 Code2 ... 

Wo 'Code1' etc. sind die Fehlercodes zwischen den doppelten Anführungszeichen in der Regex erfasst.

Ich habe das bei Windows mit Cygwin gearbeitet. Diese Lösung geht davon aus:

  1. Die Lage Ihres Perls ist /usr/bin/perl . Sie können mit $ which perl überprüfen
  2. Die Regex oben, /<Error ErrorCode="([^"]*)"/ , wie kannst du zählen.

Der Code macht …

  1. my %errors deklariert eine Karte (Hash).
  2. while (<>) jede Zeile der Eingabe iteriert und die aktuelle Zeile der eingebauten Variablen $_ zuweist.
  3. /<Error ErrorCode="([^"]*)"/ implizit versucht, gegen $_ /<Error ErrorCode="([^"]*)"/ .
  4. Wenn eine Übereinstimmung auftritt, erfassen die Klammern den Wert zwischen den doppelten Anführungszeichen und ordnen den erfassten String zu $ ​​1 zu.
  5. Die Regex "kehrt wahr" auf ein Spiel nur dann wird die Zählung inkrementiert && $countForErrorCode{$1}++ .
  6. Für die Ausgabe, iterieren Sie die erfassten Fehlercodes mit foreach my $e (keys %countForErrorCode) und drucken Sie die Zählung und den Code auf einer Zeile mit dem print "$countForErrorCode{$e} $e\n" .

Bearbeiten: detailliertere Ausgabe pro aktualisierter Spezifikation

 #!/usr/bin/perl my %dataForError; while (<>) { if (/<Error ErrorCode="([^"]+)"\s*ErrorDescription="([^"]+)"/) { if (! $dataForError{$1}) { $dataForError{$1} = {}; $dataForError{$1}{'desc'} = $2; $dataForError{$1}{'files'} = {}; } $dataForError{$1}{'count'}++; $dataForError{$1}{'files'}{$ARGV}++; } } my @out; foreach my $e (keys %dataForError) { my $files = join("\n\t", keys $dataForError{$e}{'files'}); my $out = "$dataForError{$e}{'count'}, $e, '$dataForError{$e}{'desc'}'\n\t$files\n"; push @out, $out; } print @out; 

Und wie du oben geschrieben hast, um Eingabedateien rekursiv abzurufen, kannst du dieses Skript wie:

$ find . -name "*.log" | xargs grepError.pl

Und produzieren wie:

 8, Code2, 'bang' ./today.log 48, Code4, 'oops' ./2015/jan/yesterday.log 2, Code1, 'foobar' ./2014/dec/someday.log 

Erläuterung:

  1. Das Skript kartiert jeden eindeutigen Fehlercode zu einem Hash, der die Anzahl, die Beschreibung und die eindeutigen Dateinamen verfolgt, in denen der Fehlercode gefunden wird.
  2. Perl auto-magisch speichert den aktuellen Eingabedateinamen in $ARGV .
  3. Das Skript zählt jeden einzelnen Dateinamen-Auftreten, aber gibt diese Zählungen nicht aus.
  • Python Manipulieren und Speichern von XML, ändern Sie eine Eigenschaft
  • Wie man XML-Deklaration mit xml.etree.ElementTree schreibt
  • Python ElementTree find () nicht passend in kml Datei
  • Vergleich von zwei XML-Dateien in Python
  • Einen bestimmten Wert in <type instance> erhalten
  • Python: In einem XML, wie man Knoten in einem übergeordneten Knoten löscht
  • Wie kann ich eine python-GUI mit einem XML-basierten Layout der GUI generieren?
  • Python xml ElementTree von einer String-Quelle?
  • Wie bekomme ich BeautifulSoup 4, um einen Selbstschluss zu respektieren?
  • Lesen von XML mit Python Minidom und Iteration über jeden Knoten
  • Wie erfasse ich alle Elementnamen einer XML-Datei mit LXML in Python?
  • Python ist die beste Programmiersprache der Welt.