Nous développons l'interface la plus conviviale au monde* pour la visualisation des logs

Nous développons l'interface la plus conviviale au monde* pour la visualisation des logs Si vous avez déjà utilisé des interfaces web pour consulter des journaux, vous avez probablement remarqué à quel point ces interfaces sont généralement encombrantes et (souvent) peu pratiques et réactives. On peut s'habituer à certaines, d'autres sont carrément horribles, mais il me semble que la raison de tous ces problèmes réside dans le fait que nous abordons mal la tâche de visualiser les journaux : nous essayons de créer une interface web là où il serait préférable d'utiliser la CLI (interface en ligne de commande). Personnellement, je me sens très à l'aise avec tail, grep, awk et d'autres outils, donc pour moi, l'interface idéale pour travailler avec des journaux serait quelque chose de semblable à tail et grep, mais qui pourrait également être utilisé pour lire des journaux provenant de nombreux serveurs. C'est-à-dire, bien sûr, les lire depuis ClickHouse !

*selon l'avis personnel d'un utilisateur de Habr youROCK

Découvrez logscli

Je n'ai pas trouvé de nom pour mon interface, et pour être honnête, elle existe plutôt sous forme de prototype, mais si vous souhaitez consulter immédiatement le code source, bienvenue : https://github.com/YuriyNasretdinov/logscli (350 lignes de code trié sur le volet en Go).

Fonctionnalités

Mon objectif était de créer une interface qui semblerait familière à ceux qui sont habitués à tail/grep, c'est-à-dire de supporter les éléments suivants :

  1. Consultation de tous les journaux, sans filtrage.
  2. Conserver les lignes contenant une sous-chaîne fixe (flag -F au grep).
  3. Conserver les lignes correspondant à une expression régulière (flag -E au grep).
  4. Par défaut, l'affichage est dans l'ordre chronologique inverse, car il est généralement intéressant de voir en premier les journaux les plus récents.
  5. Affichage du contexte autour de chaque ligne (paramètres -A, -B et -C au grep, qui impriment N lignes avant, après et autour de chaque ligne correspondante, respectivement).
  6. Consultation des journaux entrants en temps réel, avec ou sans filtrage (essentiellement tail -f | grep).
  7. L'interface doit être compatible avec less, head, tail et d'autres — par défaut, les résultats doivent être renvoyés sans limitation sur leur nombre ; les lignes sont imprimées en flux tant que l'utilisateur est intéressé par leur réception ; le signal SIGPIPE doit interrompre silencieusement le flux des journaux, tout comme le font tail, grep et d'autres utilitaires UNIX.

Mise en œuvre

Je vais supposer que vous savez déjà comment acheminer les journaux vers ClickHouse. Sinon, je vous recommande d'essayer lsd et kittenhouse, ainsi que cet article sur l'acheminement des journaux.

Tout d'abord, il faut définir le schéma de la base de données. Comme on veut généralement des logs triés par date, il est logique de les stocker de cette façon. S'il y a de nombreuses catégories de logs et qu'elles sont toutes similaires, on peut prendre la catégorie des logs comme première colonne de la clé primaire — cela permettra d'avoir une seule table au lieu de plusieurs, ce qui est un grand avantage lors de l'insertion dans ClickHouse (sur les serveurs à disque dur, il est recommandé d'insérer des données pas plus d'une fois par seconde. sur l'ensemble du serveur).

Donc, nous avons besoin d'un schéma de tables approximatif comme suit :

CREATE TABLE logs(
    category LowCardinality(String), -- catégorie de logs (optionnel)
    time DateTime, -- heure de l'événement
    millis UInt16, -- millisecondes (peuvent être des microsecondes, etc.) : il est recommandé de les conserver s'il y a beaucoup d'événements, afin de mieux les différencier entre eux
    ..., -- vos propres champs, comme le nom du serveur, le niveau de journalisation, etc.
    message String -- texte du message
) ENGINE=MergeTree()
ORDER BY (category, time, millis)

Malheureusement, je n'ai pas pu trouver immédiatement des sources ouvertes avec des logs réalistes que je pourrais télécharger, donc j'ai pris à la place pour exemple des avis sur des produits d'Amazon jusqu'en 2015. Bien sûr, leur structure n'est pas exactement la même que celle des logs textuels, mais cela n'est pas essentiel pour l'illustration.

guide d'importation des avis Amazon dans ClickHouse

Créons une table :

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

Dans le jeu de données d'Amazon, il n'y a que la date de l'avis, mais pas l'heure exacte, donc nous allons remplir ces données avec du hasard.

Il n'est pas nécessaire de télécharger tous les fichiers tsv et vous pouvez vous limiter aux premiers ~10-20, pour obtenir déjà un ensemble de données suffisamment grand qui ne rentrera pas dans 16 Go de mémoire vive. Pour importer les fichiers TSV, j'ai utilisé la commande suivante :

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

Sur un disque Persistent standard (HDD) de 1000 Go dans Google Cloud (j'ai choisi cette taille principalement pour une vitesse légèrement supérieure, bien qu'un SSD de la bonne taille aurait peut-être été moins cher), la vitesse de transfert était d'environ ~75 Mo/s sur 4 cœurs.

  • Je dois préciser que je travaille chez Google, mais j'ai utilisé un compte personnel et cet article n'a aucun lien avec mon travail dans l'entreprise.

Toutes les illustrations que je vais produire seront basées sur cet ensemble de données, car c'est tout ce que j'avais sous la main.

Affichage de la progression de l'analyse des données

Étant donné qu'avec ClickHouse nous allons effectuer un scan complet de la table des logs, et que cette opération peut prendre un temps considérable sans renvoyer de résultats si peu de correspondances sont trouvées, il est souhaitable de pouvoir afficher la progression de l'exécution de la requête jusqu'à l'obtention des premières lignes avec le résultat. Pour cela, il existe dans l'interface HTTP un paramètre permettant de renvoyer la progression dans les en-têtes HTTP : send_progress_in_http_headers=1. Malheureusement, la bibliothèque standard de Go ne peut pas lire les en-têtes au fur et à mesure de leur réception, mais l'interface HTTP 1.0 (à ne pas confondre avec 1.1 !) est prise en charge par ClickHouse, donc nous pouvons établir une connexion TCP brute avec ClickHouse, envoyer GET \/?query=... HTTP\/1.0nn et recevoir en réponse les en-têtes et le corps de la réponse sans aucun échappement ni chiffrement, donc dans ce cas nous n'avons même pas besoin d'utiliser la bibliothèque standard.

Streaming des logs depuis ClickHouse

ClickHouse dispose déjà d'une optimisation pour les requêtes avec ORDER BY depuis relativement longtemps (depuis 2019 ?) donc une requête comme

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

commencera immédiatement à renvoyer les lignes dont le message contient la sous-chaîne "something", sans attendre la fin de l'analyse.

Il serait aussi très pratique que ClickHouse annule automatiquement la requête lorsque la connexion est fermée, mais cela n'est pas le comportement par défaut. L'annulation automatique de la requête peut être activée avec l'option cancel_http_readonly_queries_on_client_close=1.

Gestion correcte de SIGPIPE en Go

Lorsque vous exécutez, par exemple, la commande some_cmd | head -n 10, comment exactement la commande some_cmd met-elle fin à son exécution lorsqu'elle head a lu 10 lignes ? La réponse est simple : quand head elle se termine, le pipe se ferme, et stdout de la commande some_cmd commence à pointer, en quelque sorte, "nulle part". Lorsque some_cmd elle tente d'écrire dans un pipe fermé, elle reçoit le signal SIGPIPE, qui par défaut termine silencieusement le programme..

En Go, cela se produit également par défaut, mais le gestionnaire de signal SIGPIPE affiche également "signal : SIGPIPE" ou un message similaire à la fin. Pour supprimer ce message, il suffit de gérer SIGPIPE comme nous le souhaitons, c'est-à-dire de sortir silencieusement :

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

Affichage du contexte du message

Il est souvent souhaitable de voir le contexte dans lequel une erreur s'est produite (par exemple, quelle requête a provoqué la panique, ou quels problèmes concomitants étaient visibles avant la chute), et grep les options -A, -B et -C servent à afficher le nombre spécifié de lignes après, avant, et autour du message respectivement.

Malheureusement, je n'ai pas trouvé de moyen simple de faire de même dans ClickHouse. Par conséquent, pour afficher le contexte, une requête supplémentaire est envoyée pour chaque ligne de résultat, d'un type similaire au suivant (les détails dépendent du tri et de savoir si le contexte doit être affiché avant ou après) :

SELECT time, millis, review_body FROM amazon
WHERE (time = 'TEMPS_DE_L'ÉVÉNEMENT' AND millis < MILLISECONDES_DE_L'ÉVÉNEMENT) OR (time < 'TEMPS_DE_L'ÉVÉNEMENT')
ORDER BY time DESC, millis DESC
LIMIT NOMBRE_DE_LIGNES_DE_CONTEXTE
SETTINGS max_threads=1

Étant donné que la requête est envoyée presque immédiatement après que ClickHouse a retourné la ligne correspondante, elle se retrouve dans le cache et, en général, la requête s'exécute assez rapidement en consommant un peu de CPU (en général, la requête prend environ ~6 ms sur ma machine virtuelle).

Affichage des nouveaux messages en temps réel

Pour afficher les messages entrants en temps (presque) réel, il suffit d'exécuter la requête toutes les quelques secondes, en mémorisant le dernier timestamp que nous avons rencontré auparavant.

Exemples de commandes

À quoi ressemblent des commandes typiques de logscli en pratique ?

Si vous avez téléchargé le jeu de données Amazon que j'ai mentionné au début de l'article, vous pourrez exécuter les commandes suivantes :

# Показать строки, где встречается слово 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

Liens

Le code de l'utilitaire (sans documentation) est disponible sur github à l'adresse https://github.com/YuriyNasretdinov/logscli. Je serais ravi d'entendre vos pensées concernant mon idée d'interface console pour visualiser les journaux basée sur ClickHouse.

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster