Langage d'expression commun (CEL) : comment l'entrée CEL améliore la collecte de données dans les intégrations Elastic Agent

Découvrez en quoi le Common Expression Language se distingue des autres langages de programmation, comment nous l’avons étendu pour l’entrée CEL de Filebeat, et la flexibilité qu’il vous offre pour exprimer la logique de collecte de données dans les intégrations Elastic Agent.

Agent Builder est maintenant disponible en GA. Commencez avec un essai Elastic Cloud, et consultez la documentation d'Agent Builder ici.

Les intégrations Elastic Agent permettent d’ingérer des données dans Elasticsearch à partir d’un large éventail de sources. Elles regroupent la logique de collecte, les pipelines d’ingestion, les tableaux de bord et d’autres artefacts au sein d’un package installable et administrable depuis l’interface web Kibana.

Les intégrations configurent une ou plusieurs entrées Filebeat pour assurer la collecte des données. Pour collecter des données via des API HTTP, nous avons souvent utilisé l’entrée HTTP JSON. Cependant, même les API de type listing les plus simples peuvent varier considérablement dans leurs détails. Le modèle de transformations configurées en YAML de l’entrée HTTP JSON peut alors devenir contraignant, voire parfois insuffisant pour exprimer la logique de collecte requise.

L’entrée Common Expression Language (CEL) a été introduite afin de permettre une interaction plus souple avec les API HTTP. CEL est un langage conçu pour être intégré dans des applications nécessitant un moyen rapide, sûr et extensible d’exprimer des conditions et des transformations de données. L’entrée CEL permet au créateur d’intégration d’écrire une expression unique capable de lire les paramètres, de suivre son propre état, d’effectuer des requêtes, de traiter les réponses et, au final, de renvoyer des événements prêts à être ingérés.

Dans cet article, nous examinons les différences entre CEL et d’autres langages de programmation, les extensions apportées pour l’entrée CEL, ainsi que la puissance et la souplesse qu’il apporte à l’expression de votre logique de collecte de données.

CEL et son fonctionnement dans l’entrée

CEL est un langage d’expressions. Il ne comporte pas d’instructions. Lorsque vous écrivez en CEL, vous ne décrivez pas une suite d’actions à exécuter à l’aide d’instructions. Vous indiquez plutôt la valeur à produire en rédigeant une expression. Chaque expression CEL renvoie une valeur. De petites expressions peuvent être combinées pour former une expression plus large, capable de produire un résultat selon des règles plus complexes. Nous verrons plus loin comment utiliser des expressions pour des usages généralement exprimés à l’aide d’instructions dans d’autres langages.

CEL est volontairement un langage non Turing-complet. Il n’autorise pas les boucles non bornées. Nous verrons également comment traiter des listes et des maps à l’aide de macros. En évitant les boucles non bornées, le langage garantit un temps d’exécution prévisible et limité pour chaque expression.

L’entrée CEL est configurée avec un programme CEL (une expression) et un état initial. L’état est fourni en entrée au programme. Le programme est évalué afin de produire un état de sortie. Si l’état de sortie comprend une liste d’événements, ceux-ci sont extraits puis publiés. Le reste de l’état de sortie est utilisé comme entrée pour l’évaluation suivante. Si l’état de sortie contient un ou plusieurs événements et que l’indicateur want_more: true, l’évaluation suivante est effectuée immédiatement ; sinon, l’entrée attend la fin de l’intervalle configuré avant de poursuivre. Voici un schéma simplifié du flux de contrôle de l’entrée :

La sortie de chaque évaluation est transmise comme entrée à l’évaluation suivante, tant que l’entrée s’exécute. Les données de sortie sous la clé « cursor» sont persistées sur disque et rechargées après le redémarrage de l’entrée, mais le reste de l’état n’est pas conservé entre les redémarrages.

Le langage CEL lui-même offre des fonctionnalités limitées et évite les effets de bord, mais il est extensible. L’implémentation cel-go ajoute certaines fonctionnalités, comme la prise en charge des syntaxes et des types optionnels. La bibliothèque Mito s’appuie sur cel-go et enrichit ses capacités, notamment en permettant l’exécution de requêtes HTTP. L’entrée CEL utilise la version de CEL fournie par Mito.

Travailler avec Mito

Pour créer ou déboguer une intégration à l’aide de l’entrée CEL, il est essentiel de comprendre l’état de sortie que votre programme CEL produira à partir d’un état d’entrée donné. Pendant le développement, il peut être contraignant d’exécuter votre programme CEL via l’entrée, au sein de l’ensemble de la Suite Elastic. Pour accélérer la boucle de rétroaction, vous pouvez utiliser l’outil de commande en ligne de Mito. Il vous permet d’exécuter un programme CEL directement et d’observer la sortie générée pour une entrée donnée.

Mito est écrit en Go et peut être installé comme suit :

Lorsque vous exécutez un programme CEL avec Mito, vous lui fournissez généralement deux fichiers : un fichier JSON contenant l’état d’entrée initial, et un autre fichier avec le code source de votre programme CEL :

Pour faciliter le copier-coller, les exemples de cet article sont écrits sous forme de commandes uniques qui permettent au shell de créer des fichiers temporaires à la volée, en enveloppant le contenu de chaque fichier dans <(echo '...content...'). Dans votre propre développement, travailler avec des fichiers réels sera plus facile.

Récupération des tickets depuis GitHub

L'exemple suivant inclut un programme CEL complet qui récupérera des données sur les problèmes depuis l'API GitHub. Son état d'entrée initial contient l'URL du point de terminaison de l'API et quelques informations sur la manière dont il doit gérer la pagination. Le programme CEL utilise les données dans l’état d’entrée pour générer une requête. Il va décoder la réponse, produire des événements à partir de celle-ci, et les renvoyer en tant que partie de son état de sortie.

Sa première évaluation produit la sortie suivante :

Les événements seront supprimés et, lorsqu’ils seront exécutés dans l’entrée CEL, ils seront publiés pour ingestion. Le reste de la sortie sera transmis à l’évaluation suivante du programme CEL en tant qu’état d’entrée.

Pour comprendre le fonctionnement de ce programme CEL, nous allons examiner quelques exemples CEL plus simples et détailler davantage le fonctionnement de l’entrée CEL.

Les bases de CEL

Dans le langage CEL, il n’y a pas d’instructions ; uniquement des expressions. Toute expression CEL valide est évaluée pour produire une valeur finale. Voici l’une des plus petites expressions CEL que vous puissiez écrire, ainsi que sa sortie :

De nombreuses expressions simples sont intuitives. Les opérations mathématiques ne sont prises en charge que sur des valeurs de même type (par exemple, int avec int), convertissez donc les types selon vos besoins (ici de int à double) :

Il n’y a pas de variables dans le langage CEL, mais une expression peut recevoir un nom et être utilisée dans une expression plus large grâce à la macro as de Mito. Dans cet exemple, l’expression (1 + 1) évalue la valeur 2, et .as(n, ...) donne à cette valeur le nom n pour l’utilisation dans l’expression "one plus one is "+string(n):

Il est également possible d’accumuler des informations dans une carte et de les utiliser plus tard dans l’expression, comme démontré ici avec with:

Regardez à nouveau cet exemple. Remarquez que la partie imbriquée, ({ "data": data, "size": size(data), }), nous donne la forme de la valeur finale. C'est une carte avec les clés "data" et "size". Les valeurs de ces clés dépendent de data, qui est défini par la partie extérieure de l’expression. Lire les expressions CEL de l'intérieur vers l'extérieur peut aider à voir rapidement ce qu'elles renverront.

CEL ne possède pas d’instructions de flux de contrôle, comme if, mais le branchement conditionnel peut être réalisé avec l’opérateur ternaire :

Les boucles non bornées et la récursion ne sont pas prises en charge, car CEL n’est pas un langage Turing-complet. Le temps d’exécution est donc prévisible et proportionnel à la taille des données d’entrée et à la complexité de l’expression.

Bien que les boucles non bornées ne soient pas possibles dans des expressions CEL individuelles, vous pouvez traiter des listes et des cartes à l’aide de macros comme map :

Dans cette section, nous avons abordé les points suivants :

  • Les chaînes de caractères, les nombres, les listes et les maps.
  • La concaténation de chaînes.
  • Les opérations mathématiques.
  • Le transtypage.
  • Les conditions.
  • La nomination des sous-expressions.
  • Le traitement des collections.

Ensuite, nous verrons comment effectuer des requêtes HTTP.

Requêtes

Mito étend CEL en lui donnant la possibilité d'effectuer des requêtes HTTP :

Les requêtes peuvent être construites explicitement avant leur exécution. Cela permet d’utiliser différentes méthodes HTTP et d’ajouter des en-têtes ainsi qu’un corps de requête.

Dans cet exemple, nous construisons une URL avec l’aide de format_query, ajoutons un en-tête à la requête, et analysons le corps de la réponse avec decode_json. Lorsque l'option -log_requests est sélectionnée, Mito log des informations détaillées au format JSON sur chaque demande et réponse.

Gestion de l’état et des évaluations

Maintenant que nous avons vu comment effectuer des requêtes et passé en revue les bases de CEL nécessaires pour produire l’état de sortie souhaité, examinons de plus près ce que nous devons placer dans l’état de sortie et comment cela nous permet d’orienter les traitements ultérieurs.

Le programme CEL d'une intégration doit s'assurer que son état de sortie peut être utilisé comme entrée de l'évaluation suivante. La configuration définit l'état initial, qui doit être répété dans la sortie avec toutes les modifications appropriées. Une façon simple de le faire est d’utiliser state.with({ ... }), pour répéter la carte d’état avec quelques dérogations. Un modèle courant pour les petits programmes consiste à envelopper l'ensemble du programme dans state.with(), de sorte que la propagation de l'état ne doive pas être répétée dans chaque branche qui génère des données de sortie (par exemple, succès, erreurs).

Lorsque des valeurs d’état sont initialisées par une évaluation plutôt que codées en dur dans l’état d’entrée initial, le programme devra vérifier la présence d’une valeur existante avant de définir la valeur initiale. La prise en charge de la syntaxe et des types optionnels peut aider à résoudre ce problème. En utilisant un point d'interrogation avant le nom du champ dans une clé de carte, l'accès devient facultatif : il peut ou non aboutir à une valeur, mais d'autres accès facultatifs sont possibles et il est facile de fournir une valeur par défaut si aucune valeur n'est présente :

Dans cet exemple, la valeur du compteur lue à partir de l'état est convertie en int car tous les nombres sont sérialisés dans l'état sous forme de nombres à virgule flottante, conformément aux conventions établies par JSON et le type Number de JavaScript. Il convient également de noter que "want_more": true est respecté ici par Mito, mais lorsqu’elle est exécutée dans l’entrée CEL, l’évaluation ne sera répétée que si la sortie contient également des événements.

C’est une exigence des programmes CEL exécutés par l’entrée CEL de retourner une clé "events" dans leur carte de sortie. Sa valeur peut être une liste de cartes d’événements, une liste vide ou une carte d’événement unique. Le cas d’événement unique est généralement utilisé pour les erreurs. L’événement sera publié par l’entrée, mais sa valeur sera également journalisée, et s’il définit une valeur error.message, celle-ci sera utilisée pour mettre à jour l’état de santé de la Fleet de l’intégration. Si votre programme ne produit qu’un seul événement sans erreur, il est préférable de l’inclure dans une liste.

Reprenons la sortie de notre programme de récupération des tickets GitHub présenté précédemment :

Le programme gérait effectivement son état de la manière suivante :

  • Répétition des valeurs d’état initiales dans url, per_page, et max_pages.
  • Ajout d’état qui devrait être maintenu lors des redémarrages dans cursor.page.
  • Les événements prêts à être publiés dans la liste events.
  • Demande de réévaluation immédiate avec want_more: true.

Maintenant que vous maîtrisez l’accès optionnel, la gestion de l’état, les bases de CEL et les requêtes HTTP, le programme complet de récupération des tickets GitHub devrait être plus clair. Essayez de l’exécuter avec Mito et d’expérimenter quelques modifications.

Conclusion et ressources

Dans cet article, nous avons expliqué ce qu’est le langage CEL et comment il a été étendu dans la bibliothèque Mito pour une utilisation dans l’entrée CEL. Nous avons illustré la flexibilité de CEL à travers un programme exemple qui récupère des informations sur des tickets via l’API GitHub, et détaillé les éléments nécessaires à sa compréhension : accès aux paramètres dans l’état initial, interaction avec les API HTTP, renvoi d’événements destinés à l’ingestion et gestion de l’état pour les exécutions ultérieures du programme.

Pour aller plus loin et créer des intégrations à l’aide de l’entrée CEL, plusieurs ressources méritent votre attention :

Et sans doute la ressource la plus précieuse pour créer des intégrations avec l’entrée CEL reste le code CEL des intégrations Elastic existantes, disponible sur GitHub :

cel.yml.hbs fichiers du dépôt des intégrations Elastic – GitHub

Pour aller plus loin

Prêt à créer des expériences de recherche d'exception ?

Une recherche suffisamment avancée ne se fait pas avec les efforts d'une seule personne. Elasticsearch est alimenté par des data scientists, des ML ops, des ingénieurs et bien d'autres qui sont tout aussi passionnés par la recherche que vous. Mettons-nous en relation et travaillons ensemble pour construire l'expérience de recherche magique qui vous permettra d'obtenir les résultats que vous souhaitez.

Jugez-en par vous-même