Tracks
/
Go
Go
/
Übungen
/
Logdateien parsen
Logdateien parsen

Logdateien parsen

Lernübung

Einführung

Das Paket regexp bietet Unterstützung für reguläre Ausdrücke in Go.

Syntax

Die Syntax der akzeptierten regulären Ausdrücke entspricht der allgemeinen Syntax, die auch Perl, Python und andere Sprachen verwenden.

Sowohl die Suchmuster als auch die Eingabetexte werden als UTF-8 interpretiert.

Wenn du Backticks (`) verwendest, um Strings zu erzeugen, haben Backslashes (\) keine besondere Bedeutung und markieren nicht den Anfang von Sonderzeichen wie Tabs \t oder Zeilenumbrüchen \n:

"\t\n" // regular string literal with 2 characters: a tab and a newline
`\t\n`// raw string literal with 4 characters: two backslashes, a 't', and an 'n'

Deshalb ist es sinnvoll, für reguläre Ausdrücke Backticks zu verwenden, denn so müssen wir keine Backslashes escapen:

"\\" // string with a single backslash
`\\` // string with 2 backslashes

Muster kompilieren - der Typ RegExp

Um einen regulären Ausdruck zu verwenden, müssen wir zuerst das Muster als String kompilieren. Kompilieren bedeutet hier, dass das Muster des regulären Ausdrucks als String genommen und in eine interne Darstellung umgewandelt wird, mit der sich leichter arbeiten lässt. Wir müssen jedes Muster nur einmal kompilieren; danach können wir die kompilierte Version des regulären Ausdrucks beliebig oft verwenden. Der Typ regexp.Regexp repräsentiert einen kompilierten regulären Ausdruck. Wir können ein Muster als String mit der Funktion regexp.Compile zu einem regexp.Regexp kompilieren. Diese Funktion gibt nil und einen Fehler zurück, wenn das Kompilieren fehlgeschlagen ist:

re, err := regexp.Compile(`(a|b)+`)
fmt.Println(re, err) // => (a|b)+ <nil>
re, err = regexp.Compile(`a|b)+`)
fmt.Println(re, err) // => <nil> error parsing regexp: unexpected ): `a|b)+`

Die Funktion MustCompile ist eine praktische Alternative zu Compile:

re = regexp.MustCompile(`[a-z]+\d*`)

Bei dieser Funktion musst du keinen Fehler behandeln.

Caution

MustCompile solltest du nur verwenden, wenn du sicher weißt, dass das Muster kompiliert, denn andernfalls stürzt das Programm mit einer Panic ab.

Methoden für reguläre Ausdrücke

Es gibt 16 Methoden von Regexp, die einen regulären Ausdruck abgleichen und den gefundenen Text identifizieren. Ihre Namen folgen diesem regulären Ausdruck:

Find(All)?(String)?(Submatch)?(Index)?
  • Wenn All vorhanden ist, findet die Methode aufeinanderfolgende, nicht überlappende Treffer des gesamten Ausdrucks.
  • Wenn String vorhanden ist, ist das Argument ein String, andernfalls ist es ein Slice von Bytes; die Rückgabewerte werden entsprechend angepasst.
  • Wenn Submatch vorhanden ist, ist der Rückgabewert ein Slice, der die aufeinanderfolgenden Submatches des Ausdrucks angibt.
  • Wenn Index vorhanden ist, werden Treffer und Submatches durch Byte-Index-Paare innerhalb des Eingabe-Strings angegeben.

Außerdem gibt es Methoden zum:

  • Ersetzen von Treffern regulärer Ausdrücke durch Ersatzstrings und
  • Aufteilen von Strings, die durch reguläre Ausdrücke getrennt sind.

Insgesamt definiert das Paket regexp mehr als 40 Funktionen und Methoden. Wir zeigen unten die Verwendung einiger Methoden. Weitere Einzelheiten zu diesen und anderen Funktionen findest du in der API-Dokumentation.

Beispiele für MatchString

Die Methode MatchString gibt an, ob ein String einen Treffer des regulären Ausdrucks enthält.

re = regexp.MustCompile(`[a-z]+\d*`)
b = re.MatchString("[a12]")       // => true
b = re.MatchString("12abc34(ef)") // => true
b = re.MatchString(" abc!")       // => true
b = re.MatchString("123 456")     // => false

Beispiele für FindString

Die Methode FindString gibt einen String mit dem Text des am weitesten links stehenden Treffers des regulären Ausdrucks zurück.

re = regexp.MustCompile(`[a-z]+\d*`)
s = re.FindString("[a12]")       // => "a12"
s = re.FindString("12abc34(ef)") // => "abc34"
s = re.FindString(" abc!")       // => "abc"
s = re.FindString("123 456")     // => ""

Beispiele für FindStringSubmatch

Die Methode FindStringSubmatch gibt ein Slice von Strings zurück, das den Text des am weitesten links stehenden Treffers des regulären Ausdrucks und, falls vorhanden, die Treffer seiner Unterausdrücke enthält. Damit lassen sich die Strings identifizieren, die zu Capturing-Gruppen passen. Ein Rückgabewert von nil zeigt an, dass es keinen Treffer gibt.

re = regexp.MustCompile(`[a-z]+(\d*)`)
sl = re.FindStringSubmatch("[a12]")       // => []string{"a12","12"}
sl = re.FindStringSubmatch("12abc34(ef)") // => []string{"abc34","34"}
sl = re.FindStringSubmatch(" abc!")       // => []string{"abc",""}
sl = re.FindStringSubmatch("123 456")     // => <nil>

Beispiele für ReplaceAllString

Die Methode re.ReplaceAllString(src,repl) gibt eine Kopie von src zurück und ersetzt dabei Treffer des regulären Ausdrucks re durch den Ersatz-String repl.

re = regexp.MustCompile(`[a-z]+\d*`)
s = re.ReplaceAllString("[a12]", "X")       // => "[X]"
s = re.ReplaceAllString("12abc34(ef)", "X") // => "12X(X)"
s = re.ReplaceAllString(" abc!", "X")       // => " X!"
s = re.ReplaceAllString("123 456", "X")     // => "123 456"

Beispiele für Split

Die Methode re.Split(s,n) zerlegt einen Text s in Teilstrings, die durch den Ausdruck getrennt sind, und gibt ein Slice der Teilstrings zwischen diesen Treffern zurück. Die Anzahl n bestimmt die maximale Anzahl der zurückgegebenen Teilstrings. Wenn n<0, gibt die Methode alle Teilstrings zurück.

re = regexp.MustCompile(`[a-z]+\d*`)
sl = re.Split("[a12]", -1)      // => []string{"[","]"}
sl = re.Split("12abc34(ef)", 2) // => []string{"12","(ef)"}
sl = re.Split(" abc!", -1)      // => []string{" ","!"}
sl = re.Split("123 456", -1)    // => []string{"123 456"}

Anleitung

In dieser Übung geht es um das Parsen von Logdateien.

Nach einer kürzlichen Sicherheitsüberprüfung wurdest du gebeten, die archivierten Logdateien der Organisation aufzuräumen.

Alle Strings, die an die Funktionen übergeben werden, sind garantiert nicht null und haben keine führenden oder abschließenden Leerzeichen.

1. Fehlerhafte Logzeilen erkennen

Du brauchst einen Überblick darüber, wie viele Logzeilen in deinem Archiv nicht den aktuellen Standards entsprechen. Du bist der Meinung, dass ein einfacher Test zeigt, ob eine Logzeile gültig ist. Um als gültig zu gelten, muss eine Zeile mit einem der folgenden Strings beginnen:

  • [TRC]
  • [DBG]
  • [INF]
  • [WRN]
  • [ERR]
  • [FTL]

Implementiere die Funktion IsValidLine, sodass sie false zurückgibt, wenn ein String nicht gültig ist, andernfalls true.

IsValidLine("[ERR] A good error here")
// => true
IsValidLine("Any old [ERR] text")
// => false
IsValidLine("[BOB] Any old text")
// => false

2. Die Logzeile aufteilen

Ein neues Team ist der Organisation beigetreten, und du stellst fest, dass seine Logdateien ein seltsames Trennzeichen für „Felder“ verwenden. Statt etwas Vernünftigem wie einem Doppelpunkt „:“ verwenden sie einen String wie „<--->“ oder „<=>“ (weil es hübscher aussieht), und zwar jeden String, dessen erstes Zeichen „<“ und dessen letztes Zeichen „>“ ist und der dazwischen eine beliebige Kombination der folgenden Zeichen enthält: „~“, „*“, „=“ und „-“.

Implementiere die Funktion SplitLogLine, die eine Zeile entgegennimmt und ein Array von Strings zurückgibt, von denen jeder ein Feld enthält.

SplitLogLine("section 1<*>section 2<~~~>section 3")
// => []string{"section 1", "section 2", "section 3"},

3. Die Anzahl der Zeilen zählen, die password in zitiertem Text enthalten

Das Team muss über Verweise auf Passwörter in zitiertem Text Bescheid wissen, damit sie manuell überprüft werden können.

Implementiere die Funktion CountQuotedPasswords, um einen Hinweis auf den wahrscheinlichen Umfang der manuellen Arbeit zu geben.

Identifiziere Logzeilen, in denen der String „password“, der in beliebiger Groß- und Kleinschreibung vorkommen kann, von Anführungszeichen umgeben ist. Du solltest berücksichtigen, dass zwischen den Anführungszeichen vor und nach „password“ zusätzlicher Inhalt stehen kann. Jede Zeile enthält höchstens zwei Anführungszeichen.

Zeilen, die an die Routine übergeben werden, können gültig oder ungültig sein, wie in Aufgabe 1 definiert. Wir verarbeiten sie auf die gleiche Weise, unabhängig davon, ob sie gültig sind.

lines := []string{
    `[INF] passWord`, // contains 'password' but not surrounded by quotation marks
    `"passWord"`,  // count this one
    `[INF] User saw error message "Unexpected Error" on page load.`, // does not contain 'password'
    `[INF] The message "Please reset your password" was ignored by the user`, // count this one
}
// => 2

4. Artefakte aus dem Log entfernen

Du hast festgestellt, dass eine vorgelagerte Verarbeitung der Logs den Text „end-of-line“, gefolgt von einer Zeilennummer (ohne dazwischenliegendes Leerzeichen), in den Logs verstreut.

Implementiere die Funktion RemoveEndOfLineText, die einen String entgegennimmt, den end-of-line-Text entfernt und einen „sauberen“ String zurückgibt.

Zeilen, die keinen end-of-line-Text enthalten, sollen unverändert zurückgegeben werden.

Entferne einfach den end-of-line-String. Versuche nicht, die Leerzeichen anzupassen.

RemoveEndOfLineText("[INF] end-of-line23033 Network Failure end-of-line27")
// => "[INF]  Network Failure "

5. Zeilen mit Benutzernamen versehen

Dir ist aufgefallen, dass einige der Logzeilen Sätze enthalten, die sich auf Benutzer beziehen. Diese Sätze enthalten immer den String "User", gefolgt von einem oder mehreren Leerzeichen und dann einem Benutzernamen. Du beschließt, solche Zeilen zu markieren.

Implementiere eine Funktion TagWithUserName, die Logzeilen verarbeitet:

  • Zeilen, die den String "User " nicht enthalten, bleiben unverändert.
  • Bei Zeilen, die den String "User " enthalten, stellst du der Zeile [USR] gefolgt vom Benutzernamen voran.

Zum Beispiel:

result := TagWithUserName([]string{
    "[WRN] User James123 has exceeded storage space.",
	"[WRN] Host down. User   Michelle4 lost connection.",
	"[INF] Users can login again after 23:00.",
	"[DBG] We need to check that user names are at least 6 chars long.",
})
// => []string {
//  "[USR] James123 [WRN] User James123 has exceeded storage space.",
//  "[USR] Michelle4 [WRN] Host down. User   Michelle4 lost connection.",
//  "[INF] Users can login again after 23:00.",
//  "[DBG] We need to check that user names are at least 6 chars long."
// }

Du kannst davon ausgehen, dass:

  • auf Benutzernamen im Log mindestens ein Whitespace-Zeichen folgt.
  • in jeder Zeile höchstens ein Vorkommen des Strings "User " gibt.
  • Benutzernamen nicht leere Strings sind, die kein Whitespace enthalten.
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
Go Exercism

Bereit, mit Logdateien parsen zu starten?

Melde dich bei Exercism an, um Go mit 34 Konzepte165 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.