Wir entwickeln die benutzerfreundlichste Benutzeroberfläche der Welt* für die Anzeige von Protokollen

Wir entwickeln die benutzerfreundlichste Benutzeroberfläche der Welt* für die Anzeige von Protokollen Wenn Sie jemals Webschnittstellen zum Anzeigen von Protokollen verwendet haben, haben Sie sicherlich bemerkt, wie oft diese Schnittstellen schwerfällig und (oft) nicht besonders benutzerfreundlich oder reaktionsschnell sind. An einige kann man sich gewöhnen, andere sind völlig schrecklich, aber ich denke, das Problem liegt darin, dass wir den Ansatz zum Anzeigen von Protokollen falsch angehen: Wir versuchen, eine Webschnittstelle dort zu schaffen, wo CLI (Command Line Interface) besser funktioniert. Persönlich arbeite ich sehr gerne mit tail, grep, awk und anderen Tools, und ein idealer Schnittstelle für die Arbeit mit Protokollen wäre für mich etwas Ähnliches wie tail und grep, aber das man nutzen kann, um Protokolle von vielen Servern zu lesen. Das heißt, natürlich sie aus ClickHouse zu lesen!

*meine persönliche Meinung eines Habr-Nutzers youROCK

Begrüßen Sie logscli

Ich habe dem Schnittstelle keinen Namen gegeben, und ehrlich gesagt existiert er eher als Prototyp, aber wenn Sie die Quellcodes sofort ansehen möchten, sind Sie herzlich eingeladen: https://github.com/YuriyNasretdinov/logscli (350 Zeilen ausgewählten Codes in Go).

Funktionen

Ich hatte das Ziel, eine Schnittstelle zu schaffen, die denen vertraut erscheint, die an tail/grep gewöhnt sind, d.h. die folgenden Dinge unterstützen:

  1. Anzeigen aller Protokolle ohne Filterung.
  2. Behalten Sie die Zeilen, die eine bestimmte Teilzeichenfolge enthalten (Flagge -F arbeiten grep).
  3. Behalten Sie die Zeilen, die zu einem regulären Ausdruck passen (Flagge -E arbeiten grep).
  4. Standardmäßig erfolgt die Anzeige in umgekehrter chronologischer Reihenfolge, da in der Regel zunächst die neuesten Protokolle von Interesse sind.
  5. Anzeigen des Kontexts um jede Zeile (Parameter -A, -B und -C arbeiten grep, die N Zeilen davor, danach und um jede passende Zeile herum ausdrucken).
  6. Anzeigen von eingehenden Protokollen in Echtzeit, mit und ohne Filterung (basically tail -f | grep).
  7. Die Schnittstelle sollte mit less, head, tail und anderen kompatibel sein — standardmäßig sollten Ergebnisse ohne Einschränkungen in ihrer Anzahl zurückgegeben werden; Zeilen werden streamweise gedruckt, solange der Benutzer daran interessiert ist; das Signal SIGPIPE sollte stillschweigend das Streaming der Protokolle unterbrechen, genau wie es bei tail, grep und anderen UNIX-Dienstprogrammen geschieht.

Implementierung

Ich gehe davon aus, dass Sie bereits irgendwie in der Lage sind, Protokolle nach ClickHouse zu befördern. Wenn nicht, empfehle ich, lsd und kittenhouse, sowie diesen Artikel über das Bereitstellen von Protokollen.

Zunächst müssen wir uns über das Schema der Datenbank einig werden. Da Logs in der Regel zeitlich sortiert werden möchten, erscheint es logisch, sie auch so zu speichern. Wenn es viele Log-Kategorien gibt und diese alle ähnlich sind, kann die Kategorie des Logs als erste Spalte des Primärschlüssels verwendet werden - dies ermöglicht es, eine Tabelle anstelle von mehreren zu haben, was beim Einfügen in ClickHouse ein großer Vorteil ist (auf Servern mit Festplatten wird empfohlen, Daten nicht häufiger als etwa einmal pro Sekunde einzufügen. für den gesamten Server).

Das bedeutet, wir benötigen ungefähr folgendes Tabellenschema:

CREATE TABLE logs(
    category LowCardinality(String), -- Log-Kategorie (optional)
    time DateTime, -- Zeitpunkt des Ereignisses
    millis UInt16, -- Millisekunden (es können auch Mikrosekunden und so weiter sein): empfohlen zu speichern, wenn viele Ereignisse vorhanden sind, um diese besser unterscheiden zu können
    ..., -- Ihre eigenen Felder, z.B. Servername, Logging-Level usw.
    message String -- Text der Nachricht
) ENGINE=MergeTree()
ORDER BY (category, time, millis)

Leider konnte ich nicht sofort einige offene Quellen mit realistischen Logs finden, die man herunterladen könnte, also habe ich stattdessen als Beispiel genommen Bewertungen von Produkten aus Amazon bis 2015. Natürlich ist ihre Struktur nicht ganz dieselbe wie die von Textlogs, aber für die Veranschaulichung ist das nicht entscheidend.

Anleitung zum Hochladen von Amazon-Bewertungen in ClickHouse

Erstellen wir die Tabelle:

CREATE TABLE amazon(
   review_date Date,
   time DateTime DEFAULT toDateTime(toUInt32(review_date) * 86400 + rand() % 86400),
   millis UInt16 DEFAULT rand() % 1000,
   marketplace LowCardinality(String),
   customer_id Int64,
   review_id String,
   product_id LowCardinality(String),
   product_parent Int64,
   product_title String,
   product_category LowCardinality(String),
   star_rating UInt8,
   helpful_votes UInt32,
   total_votes UInt32,
   vine FixedString(1),
   verified_purchase FixedString(1),
   review_headline String,
   review_body String
)
ENGINE=MergeTree()
ORDER BY (time, millis)
SETTINGS index_granularity=8192

Im Amazon-Datensatz gibt es nur das Datum für die Bewertung, aber keine genaue Uhrzeit, daher füllen wir diese Daten mit Zufallszahlen.

Man kann alle tsv-Dateien herunterladen, muss sich aber auf die ersten ~10-20 beschränken, um bereits ein großes Datenset zu erhalten, das nicht in 16 GB Arbeitsspeicher passt. Zum Hochladen der TSV-Dateien habe ich den folgenden Befehl verwendet:

for i in *.tsv; do
    echo $i;
    tail -n +2 $i | pv |
    clickhouse-client --input_format_allow_errors_ratio 0.5 --query='INSERT INTO amazon(marketplace,customer_id,review_id,product_id,product_parent,product_title,product_category,star_rating,helpful_votes,total_votes,vine,verified_purchase,review_headline,review_body,review_date) FORMAT TabSeparated'
done

Auf einer Standard-Persistent Disk (HDD) in Google Cloud mit einer Größe von 1000 GB (diese Größe habe ich hauptsächlich gewählt, um die Geschwindigkeit etwas zu erhöhen, obwohl möglicherweise eine SSD in der benötigten Größe günstiger gewesen wäre) betrug die Upload-Geschwindigkeit ungefähr ~75 MB/s bei 4 Kernen.

  • Ich muss anmerken, dass ich bei Google arbeite, aber ich habe ein persönliches Konto verwendet und dieser Artikel hat nichts mit meiner Arbeit im Unternehmen zu tun.

Alle Abbildungen werde ich genau mit diesem Datensatz erstellen, da dies alles ist, was ich zur Hand hatte.

Anzeige des Fortschritts des Datenscans

Da wir in ClickHouse einen Vollscan der Tabelle mit den Logs durchführen werden und diese Operation signifikante Zeit in Anspruch nehmen kann, ohne zunächst Ergebnisse zu liefern, falls nur wenige Übereinstimmungen gefunden werden, ist es wünschenswert, den Fortschritt der Anfrage bis zum Erhalt der ersten Ergebnisse anzuzeigen. Dafür gibt es im HTTP-Interface einen Parameter, der es ermöglicht, den Fortschritt in den HTTP-Headern zurückzugeben: send_progress_in_http_headers=1. Leider kann die Standardbibliothek von Go die Header nicht während ihres Erhalts lesen, aber das HTTP 1.0 Interface (nicht verwechseln mit 1.1!) wird von ClickHouse unterstützt, sodass man eine rohe TCP-Verbindung zu ClickHouse aufbauen kann, um zu senden GET \/?query=... HTTP\/1.0nn und als Antwort die Header und den Antwortkörper ohne jegliche Escapierung oder Verschlüsselung zu erhalten, sodass wir in diesem Fall die Standardbibliothek nicht einmal verwenden müssen.

Streaming von Logs aus ClickHouse

In ClickHouse gibt es schon relativ lange (seit 2019?) Optimierungen für Abfragen mit ORDER BY, sodass eine Abfrage vom Typ

SELECT time, millis, message
FROM logs
WHERE message LIKE '%something%'
ORDER BY time DESC, millis DESC

sofort Zeilen zurückgeben wird, die in der Nachricht die Teilzeichenfolge "something" enthalten, ohne auf das Ende des Scans zu warten.

Außerdem wäre es sehr praktisch, wenn ClickHouse die Anfrage selbst abbrechen würde, wenn die Verbindung geschlossen wird, doch das ist nicht das Standardverhalten. Die automatische Abbruch der Anfrage kann mit der Option aktiviert werden cancel_http_readonly_queries_on_client_close=1.

Korrekte Verarbeitung von SIGPIPE in Go

Wenn Sie beispielsweise den Befehl ausführen some_cmd | head -n 10, auf welche Weise beendet der Befehl some_cmd seine Ausführung, wenn head er 10 Zeilen gelesen hat? Die Antwort ist einfach: wenn head der Befehl beendet wird, schließt sich das Pipe, und stdout des Befehls some_cmd zeigt dann, salopp gesagt, "nirgendwo" hin. Wenn some_cmd ein Versuch unternommen wird, in ein geschlossenes Pipe zu schreiben, erhält es ein SIGPIPE-Signal, das die Anwendung standardmäßig still beendet..

In Go geschieht dies standardmäßig ebenfalls, aber der SIGPIPE-Signalhandler gibt am Ende auch "signal: SIGPIPE" oder eine ähnliche Nachricht aus, und um diese Nachricht zu vermeiden, muss man SIGPIPE so behandeln, wie man möchte, also einfach still und leise beenden:

ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGPIPE)
go func() {
    <-ch
    os.Exit(0)
}()

Anzeigen des Nachrichtenkontexts

Oft möchte man den Kontext sehen, in dem ein Fehler aufgetreten ist (zum Beispiel, welcher Request die Panik ausgelöst hat oder welche begleitenden Probleme vor dem Absturz sichtbar waren), und dafür grep dienen die Optionen -A, -B und -C, die die angegebene Anzahl an Zeilen nach, vor und um die Nachricht herum anzeigen.

Leider habe ich keinen einfachen Weg gefunden, dasselbe in ClickHouse zu tun, daher wird zur Anzeige des Kontexts für jede Zeile des Ergebnisses eine zusätzliche Anfrage gesendet, die ungefähr folgenden Typ hat (die Details hängen von der Sortierung ab und davon, ob der Kontext vor oder nach angezeigt wird):

SELECT time,millis,review_body FROM amazon
WHERE (time = 'EREIGNISZEIT' AND millis < EREIGNIS_MILLISEN) OR (time < 'EREIGNISZEIT')
ORDER BY time DESC, millis DESC
LIMIT KONTEKST_ZEILENZAHL
SETTINGS max_threads=1

Da die Anfrage fast sofort gesendet wird, nachdem ClickHouse die entsprechende Zeile zurückgegeben hat, wird sie im Cache gespeichert und insgesamt wird die Anfrage relativ schnell ausgeführt und verbraucht ein wenig CPU (in der Regel dauert die Anfrage etwa ~6 ms auf meiner virtuellen Maschine).

Echtzeit-Anzeige neuer Nachrichten

Um eingehende Nachrichten (fast) in Echtzeit anzuzeigen, führen wir einfach alle paar Sekunden eine Anfrage aus und merken uns den letzten Timestamp, den wir vorher gesehen haben.

Wie sehen typische logscli-Befehle in der Praxis aus?

Wie sehen typische logscli-Befehle in der Praxis aus?

Wenn Sie das Amazon-Dataset heruntergeladen haben, das ich zu Beginn des Artikels erwähnt habe, können Sie die folgenden Befehle ausführen:

# Показать строки, где встречается слово walmart
$ logscli -F 'walmart' | less

# Показать самые свежие 10 строк, где встречается "terrible"
$ logscli -F terrible -limit 10

# То же самое без -limit:
$ logscli -F terrible | head -n 10

# Показать все строки, подходящие под /times [0-9]/, написанные для vine и у которых высокий рейтинг
$ logscli -E 'times [0-9]' -where="vine='Y' AND star_rating>4" | less

# Показать все строки со словом "panic" и 3 строки контекста вокруг
$ logscli -F 'panic' -C 3 | less

# Непрерывно показывать новые строки со словом "5-star"
$ logscli -F '5-star' -tailf

Links

Der Utility-Code (ohne Dokumentation) ist auf GitHub zu finden unter https://github.com/YuriyNasretdinov/logscli. Ich freue mich auf Ihre Gedanken zu meiner Idee für eine Konsolenschnittstelle zur Anzeige von Logs basierend auf ClickHouse.

Quelle: habr.com

60GB SSD 8Gb DDR4