tester - java split regex




Protokolle mit Regex in Java filtern (2)

Die Beschreibung ist ziemlich lang, also bitte mit mir:
Ich habe Protokolldateien von 300 MB bis 1,5 GB Größe, die mit einem Suchschlüssel gefiltert werden müssen.

Das Format der Protokolle ist ungefähr so:

24 May 2017 17:00:06,827 [INFO] 123456 (Blah : Blah1) Service-name:: Single line content
24 May 2017 17:00:06,828 [INFO] 567890 (Blah : Blah1) Service-name:: Content( May span multiple lines)
24 May 2017 17:00:06,829 [INFO] 123456 (Blah : Blah2) Service-name: Multiple line content. Printing Object[ ID1=fac-adasd ID2=123231
ID3=123108 Status=Unknown
Code=530007 Dest=CA
]
24 May 2017 17:00:06,830 [INFO] 123456 (Blah : Blah1) Service-name:: Single line content
4 May 2017 17:00:06,831 [INFO] 567890 (Blah : Blah2) Service-name:: Content( May span multiple lines)

Mit dem Suchschlüssel 123456 muss ich folgendes holen:

24 May 2017 17:00:06,827 [INFO] 123456 (Blah : Blah1) Service-name:: Single line content
24 May 2017 17:00:06,829 [INFO] 123456 (Blah : Blah2) Service-name: Multiple line content. Printing Object[ ID1=fac-adasd ID2=123231
ID3=123108 Status=Unknown
Code=530007 Dest=CA
]
24 May 2017 17:00:06,830 [INFO] 123456 (Blah : Blah1) Service-name:: Single line content

Das folgende awk-Skript erledigt meine Aufgabe (sehr langsam):

gawk '/([0-9]{1}|[0-9]{2})\s\w+\s[0-9]{4}/{n=0}/123456/{n=1} n'

Es dauert ungefähr 8 Minuten, um eine Protokolldatei mit einer Größe von 1 GB zu durchsuchen. Und ich muss das für viele solche Dateien tun. Um das Ganze abzurunden, habe ich mehrere solcher Suchschlüssel, was die ganze Aufgabe unmöglich macht.

Meine erste Lösung ist Multithreading. Ich habe einen FixedThreadPoolExecutor verwendet, eine Aufgabe für jede Datei eingereicht, die gefiltert werden muss. In der Aufgabenbeschreibung habe ich einen neuen Prozess mit javas Runtime () erzeugt, der das gawk-Skript mit bash ausführen und die Ausgabe in eine Datei schreiben und dann alle Dateien zusammenführen würde.

Obwohl dies eine schlechte Methode zu sein scheint, da die Filterung von der E / A-Funktion abhängig ist, anstatt von der CPU, führte dies zu einer Beschleunigung im Vergleich zur sequenziellen Ausführung des Skripts für jede Datei.

Aber es ist immer noch nicht genug, da die ganze Sache dauert 2 Stunden, für einen einzigen Suchschlüssel, mit 27 GB Log-Dateien. Im Durchschnitt habe ich 4 solche Suchschlüssel und muss alle ihre Ergebnisse abrufen und zusammensetzen.

Meine Methode ist nicht effizient, weil:

A) Es greift mehrfach auf jede Protokolldatei zu, wenn mehrere Suchschlüssel angegeben werden, und verursacht noch mehr I / O-Overhead.
B) Es entsteht der Aufwand, einen Prozess innerhalb jedes Threads zu erstellen.

Eine einfache Lösung für all dies ist die Abkehr von awk und das Ausführen der ganzen Sache in Java mit einer Regex-Bibliothek. Die Frage ist, was ist diese Regex-Bibliothek, die mir die gewünschte Ausgabe liefern könnte?
Mit awk habe ich die Eigenschaft /filter/{action} der ich einen Bereich von mehreren Zeilen festlegen kann, die erfasst werden sollen (wie oben zu sehen). Wie kann ich das gleiche in Java machen?

Ich bin offen für alle möglichen Vorschläge. Eine extreme Option wäre beispielsweise, die Protokolldateien in einem gemeinsam genutzten Dateisystem wie S3 zu speichern und die Ausgabe mit mehreren Computern zu verarbeiten.

Ich bin neu in stackoverflow und ich weiß nicht einmal, ob ich das hier posten kann. Aber ich arbeite seit einer Woche daran und ich brauche jemanden mit Erfahrung, der mich dabei unterstützt. Danke im Voraus.


Sie haben ein paar Optionen.

Das beste wäre, ein invertiertes Wörterbuch zu verwenden. Das bedeutet, dass Sie für jedes in mindestens einem der Protokolle enthaltene Schlüsselwort x einen Verweis auf alle Protokolle, die es enthalten, speichern. Aber da Sie bereits eine Woche mit dieser Aufgabe beschäftigt waren, würde ich Ihnen raten, etwas zu verwenden, das bereits da ist und genau das tut: Elasticsearch . Sie können den gesamten ELK-Stack (elasticsearch, logstash, kibana - speziell für Logs) verwenden, um die Logs sogar zu parsen, da Sie einfach einen Regex-Ausdruck in die Konfigurationsdatei einfügen können. Sie müssen die Dateien nur einmal indizieren und erhalten Suchvorgänge in wenigen Millisekunden.

Wenn Sie wirklich Energie verschwenden wollen und nicht die beste Lösung suchen, können Sie map-reduce auf hadoop verwenden, um das Protokoll zu filtern. Aber das ist keine Aufgabe, bei der map-reduce optimal ist und eher wie ein Hack aussehen würde.


Die Umstellung auf Java ist möglicherweise nicht die beste Option, wenn Sie Ihre Ausführungszeit verkürzen möchten, aber wenn Sie darüber nachdenken, habe ich eine Java-Klasse geschrieben, die Ihnen helfen könnte.

Sie können damit einen oder mehrere Schlüssel in einer Datei gleichzeitig suchen. Da Sie eine Protokolldatei lesen, kann davon ausgegangen werden, dass alle Zeilen dem korrekten Format ohne Fehler folgen. Anstatt also die gesamte Zeile im Regex-Format zu überprüfen, springt sie einfach dorthin, wo der Schlüssel sein sollte (die Ziffern nach dem ersten), und vergleicht sie mit dem erforderlichen Wert (vorausgesetzt, es handelt sich immer um eine Zahl).

Benutze es so:

Set<Integer> keys = new HashSet();
keys.add(123456);
keys.add(314159);
/* synchronously (omitting 3rd argument prints to stdout) */
new KeySearch('path/to/file.log', keys).run();

/* asynchronously!!! (to use PrintStream, create the output file first) */
PrintStream ps1 = new PrintStream('lines-found1.log');
PrintStream ps2 = new PrintStream('lines-found2.log');
new Thread(new KeySearch('path/to/1.log', keys, ps1::println)).start();
new Thread(new KeySearch('path/to/2.log', keys, ps2::println)).start();

Das dritte Argument ist eine benutzerdefinierte Schnittstelle KeySearch.Callback die Zeilen empfängt, wie sie gefunden werden. Ich verwende eine Methodenreferenz als Beispiel, aber es kann alles sein, was Sie wollen. Hier ist die Klasse (benötigt mindestens Java 8).

import java.io.*;
import java.util.*;

public class KeySearch implements Runnable {
    public interface Callback { 
        void lineFound(String line); 
    }

    private final Set<Integer> keys;
    private final Callback callback;
    private final String name;

    public KeySearch(String fileName, Collection<Integer> keys) {
        this(fileName, keys, System.out::println);
    }

    public KeySearch(String fileName, Collection<Integer> keys, Callback call) {
        this.keys = new HashSet<>(keys);
        this.name = fileName;
        this.callback = call;
    }

    @Override
    public void run() {
        String s;
        try(FileReader fr = new FileReader(name); 
                BufferedReader br = new BufferedReader(fr)) {
            while ((s = readLine(br)) != null)
                if (matches(s)) callback.lineFound(s);
        } catch (IOException e) {
            System.err.println("Error reading " + name);
            throw new RuntimeException(e);
        }
    }

    private boolean matches(String line) {
        return keys.contains(getKeyOf(line));
    }

    private String readLine(BufferedReader reader) throws IOException {
        StringBuilder line = new StringBuilder();
        String next;

        do {
            next = reader.readLine();
            if (next == null) return null;
            line.append(next).append(System.lineSeparator());
        } while (next.lastIndexOf('[') > next.lastIndexOf(']'));

        return line.toString();
    }

    private boolean isDigit(CharSequence s, int i) {
        char c = s.charAt(i);
        return c >= '0' && c <= '9';
    }

    private int getKeyOf(String line) {
        // find the first ] (e.g. at the end of [INFO])
        // and read the first number after it
        int start = line.indexOf(']');
        while (!isDigit(line, start)) start++;

        int end = start;
        while (isDigit(line, end)) end++;

        return Integer.parseInt(line.substring(start, end));
    }
}




awk