Laden...

LogFile mit RegEx parsen

Erstellt von inflames2k vor 7 Jahren Letzter Beitrag vor 7 Jahren 3.105 Views
inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 7 Jahren
LogFile mit RegEx parsen

Hallo,

ich schreibe gerade eine kleine Funktion um Logfiles eines externen Programmes zu Parsen. Das LogFile hat den folgenden Aufbau (je Eintrag):
INFO (2016-04-14 09:15:03,374 - Synthetis.Prospeo.Connector.Loader) [Forced listening port to [9083] in .config file]


ENTRYTYPE (2017-06-02 10:12:22,625 - LogSource) [Logmeldung] Exceptiontext

Mit einem Beispieleintrag sähe das z.B. so aus:


INFO (2017-06-02 10:12:22,625 - LOADER) [Forced listening port to [9083] in .config file] 

Der Exception Text ist optional und schließt am Ende des Eintrags an.

Ich habe schon mittels Regulären Ausdruck die einzelnen Einträge separiert. Auch das Auslesen mittels Regex.Match() klappt gut. - Problem ist nun, dass es Logmeldungen gibt wie im Beispiel, wo im Meldungstext noch einmal eckige Klammern auftauchen. Derzeit sieht mein Regex um die Meldung zu holen wie folgt aus:


\[(.{1,}?)\]

Wie müsste ein Regex aussehen, der alles aus "[Forced listening port to [9083] in .config file]" herausliest? Also auch den Port 9083 ohne bei dem schließenden "]" vom Port abzubrechen.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

3.003 Beiträge seit 2006
vor 7 Jahren

\[(.+)\]

(Getestet.)

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

709 Beiträge seit 2008
vor 7 Jahren

Moin inflames2k,
wenn ich mich recht entsinne, dann müsste das mit Balancing Groups möglich sein.

3.003 Beiträge seit 2006
vor 7 Jahren

Nicht nötig. Nur der überflüssige Quantifier ("?") in seiner regex hat ihm das Genick gebrochen.


var regex = new Regex(@"^(?<type>[A-Z]+)\s\((?<time>.*)\s+-\s+(?<source>.*)\)\s\[(?<message>.+)\]$");
var result = regex.Match(@"INFO (2017-06-02 10:12:22,625 - LOADER) [Forced listening port to [9083] in .config file]");

new [] { "type", "time", "source", "message" }.ToList().ForEach(p => Console.WriteLine(result.Groups[p]));

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 7 Jahren

Ah, ich danke euch. 😉

Wald und Bäume... und Regex die mich sowieso nicht mögen. Oder ich nicht. Wie auch immer, ich danke euch.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 7 Jahren

Ich muss hier noch einmal einhaken. Leider stehen die Inhalte nicht Zeilenweise in der Datei. Das heißt, bei der Message kann es vorkommen, dass diese Mehrzeilig sind. In dem Fall findet er nichts. Gleiches gilt für den Exception Text der am Ende anschließt.

Beispiel:> Fehlermeldung:

ERROR (2016-04-27 09:33:59,388 - ExecitionEngine) [Error while running module] System.Threading.ThreadAbortException: Thread was being aborted. at MyModule.Start()
ERROR (2016-04-27 09:33:59,388 - ExecutionEngine) [Error while running module]
System.Threading.ThreadAbortException: Thread was being aborted.
at MyModule.Start()
System.Threading.ThreadAbortException: Thread was being aborted.
at Synthetis.Prospeo.Connector.Connector.Start()
ERROR (2016-04-27 09:33:59,388 - ExecutionEngine) [Error while sending production data: System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it 127.0.0.1:82
at System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot, SocketAddress socketAddress)
at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult, Int32 timeout, Exception& exception)
--- End of inner exception stack trace ---
at System.Net.HttpWebRequest.GetRequestStream(TransportContext& context)
at System.Net.HttpWebRequest.GetRequestStream()
at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)]

Der erste Eintrag wird mit Hilfe des regulären Ausdrucks gefunden:


(?<type>[A-Z]+)\s\((?<time>.*)\s+-\s+(?<source>.*)\)\s\[(?<message>.+)\](?<exception>.+)

Der Zweite Eintrag wird nur bis "[Error while running module]" gefunden. Alles weitere findet der Regex nicht. Der dritte Eintrag wird garnicht gefunden, da bereits Zeilenumbrüche im Message-Teil stehen.

Könnt ihr mir hier eventuell noch einmal weiterhelfen?

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

49.485 Beiträge seit 2005
vor 7 Jahren

Hallo inflames2k,

damit . auch auf Zeilenumbrüche passt, musst du RegexOptions.SingleLine verwenden.

Allerdings würde dann dein Pattern bis zum Ende der Datei passen.

Damit der Pattern nur bis zum Beginn des nächsten Eintrages passt, braucht du noch einen negativen Lookahead. Siehe dazu RegEx kürzester Match [und die Gefahren von .*?].

herbivore

PS: Es könnte jedoch einfacher sein, per Regex erstmal den Beginn aller Einträge zu finden und jeweils dort zu splitten, so dass man jeden Eintrag als einzelnen (ggf. mehrzeiligen) String hat. Dann kannst du immer noch einen Pattern bauen, der innerhalb eines Eintrag alles raussucht, was du brauchst, ohne die Gefahr über die Grenzen von Einträgen hinaus zu matchen.

2.080 Beiträge seit 2012
vor 7 Jahren

Meine Variante:
https://regex101.com/r/tEVCZ3/1

Das hat allerdings auch die Schwäche, dass es zu viel erkennt.
Ich würde den Exception-Text aber nicht weiter einschränken, also .* weil in einer Exception auch mal mehr drin stehen kann.
Der Rest wird mMn. richtig erkannt.

Meine Idee: \A (siehe hier)
Die Überlegung war, das an den Anfang zusetzen, damit ein Match nur am Anfang einer Zeile beginnen darf. Oder noch zusätzlich: Ein Match darf nur am Ende einer Zeile (\Z, Link so.) enden.

Leider erkennt er das nicht wie erhofft
Hat da jemand eine Idee?

Mit einem negativen Lookahead hat's bei mir nicht geklappt, das Pattern dazu hab ich aber nicht gespeichert.

Eine Alternative, die auch zum Ziel führen könnte:
Du lässt den Exception-Text ganz raus, nimmst dir aber die Position der Matches.
Du weißt, wo ein Match beginnt und wie lang es ist.
Dann kannst Du den Text zwischen der End-Position eines Matches bis zur Start-Position des nächsten Matches nehmen und hast den Exception-Text.
Dafür muss das Flag SingleLine aber wieder raus.

Bei mir sähe das dann so aus:
https://regex101.com/r/tEVCZ3/2

Match 1: Zeichen 0 bis 60
Match 2: Zeichen 78 bis 167
(Siehe die Übersicht rechts bei regex101)

Der Text von Zeichen 61 bis 77 ist dann der Exception-Text zum ersten Match.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 7 Jahren

Hallo,

ich bin den Weg übers Splitten (wie von herbivore vorgeschlagen) gegangen. Ich splitte den Inhalt des Logfiles wie folgt:


string[] splittedLog = Regex.Split(log, @"([ABDEFGILNORTUW]{4,5})\s\("); 
// Abbildung der einzelnen LOGLEVEL 

Damit erhalte ich die Daten in Auswertbarer Form. - Jeweils ein Eintrag im Array mit dem Entrytype und einen mit den restlichen Informationen. Diese lassen sich in einer Schleife ziemlich auswerten.


Regex regex = new Regex(@"(?<time>.*)\s+-\s+(?<source>.*)\)\s\[(?<message>.+)\](?<exception>.+)", RegexOptions.Singleline);

for (int i = 1; i < splittedLog.Length; i+=2)
{
      LogEntry entry = new LogEntry();
      entry.EntryType = this.ParseType(splittedLog[i]);

      Match match = regex.Match(splittedLog[i+1]);
      entry.TimeWritten = DateTime.ParseExact(match.Groups["time"].Value, "yyyy-MM-dd HH:mm:ss,fff", null);
      entry.LogMessage = match.Groups["message"].Value;
      entry.Source = match.Groups["source"].Value;
      entry.Exception = match.Groups["exception"].Value;
      entries.Add(entry);
}

@Palin: Danke für deine Überlegungen. Da aber auch die Meldung mehrzeilig sein kann, greift deine letzte Variante leiter auch nicht ganz wie erwartet. - Um das ganze nicht in die Länge zu ziehen, bleibe ich nun bei den gesplitteten Daten.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

2.080 Beiträge seit 2012
vor 7 Jahren

Das die Mehrzeilig sein kann, ist mit drin, wenn Du die Positionen auf den ganzen Text betrachtest.
Allerdings mache ich dadurch im Prinzip genau das, was Regex.Split tut - das hab ich nicht gesehen 😄

Dann würde ich da aber noch den Timestamp beim Split-Pattern mit auf nehmen.
Das Pattern, wie Du es hast, könnte z.B. auch im Exception-Text irgendwo auftauchen.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

49.485 Beiträge seit 2005
vor 7 Jahren

Hallo inflames2k,

so wie ich es verstehe, muss der ENTRYTYPE immer am Anfang der Zeile stehen. In Regex matcht das Dach (^) auf den Anfang einer (bzw. jeder) Zeile, wenn man RegexOptions.Multiline verwendet.

Außerdem finde ich [ABDEFGILNORTUW]{4,5} etwas merkwürdig. Ich würde eher sowas verwenden:

^(ERROR|INFO|...)

wobei ... für die weiteren möglichen ENTRYTYPEs steht.

Wenn du flexibel bleiben willst, dann müsstest du eher sowas verwenden

^[A-Z]{x,y}

wobei x für die minimale und y für die maximale (je zu erwartende) Länge eines ENTRYTYPEs steht.

herbivore

inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 7 Jahren

Hallo herbivore,

so wie ich es verstehe, muss der ENTRYTYPE immer am Anfang der Zeile stehen. In Regex matcht das Dach (^) auf den Anfang einer (bzw. jeder) Zeile, wenn man RegexOptions.Multiline verwendet.

Das werde ich noch anpassen, da ich annehme, dass es doch einen Performance-Gewinn ausmachen würde.

Außerdem finde ich [ABDEFGILNORTUW]{4,5} etwas merkwürdig.

Mit den Zeichen decke ich alle verfügbaren ENTRYTYPEs ab, deswegen habe ich mich dafür entschieden. - Bin mir noch nicht sicher, ob ich das anpasse. Lesbarer ist dein Vorschlag alle male.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

D
985 Beiträge seit 2014
vor 7 Jahren

Bei diesem Aufbau der Logdatei wird man sich generell davon verabschieden müssen eine 100% funktionierende Lösung zu bekommen. Es gibt einige Situationen, wo die Erkennung aus dem Tritt kommen könnte.

Dessen sollte man sich bewusst sein oder (wenn möglich) den Logwriter anpassen.

3.003 Beiträge seit 2006
vor 7 Jahren

Unter Umständen bist du sogar auf der sichereren Seite mit einem einfachen stringsplit. Bis zur Logmeldung sollte, soweit ich das sehen kann, die Anzahl der Leerzeichen bei jedem Logeintrag identisch sein, und ab da kannst du es wieder joinen. string.split und string.join sind beide wirklich schnell, insofern vergibst du dir nix. Nur als Gedanke 😃.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)