13 décembre 2016 Technique

Utilisation de Painless avec les « scripted fields » Kibana

Par Tanya Bragin

Kibana offre un moyen puissant de faire des recherches et de visualiser des données stockées dans Elasticsearch. Pour les visualisations, Kibana recherche les champs définis dans les mappings d'Elasticsearch et les propose en tant qu'options à l'utilisateur qui souhaite créer un graphique. Mais que se passe-t-il si vous oubliez de définir une valeur importante dans un champ séparé de votre schéma ? Ou si vous voulez combiner deux champs et les traiter comme un seul ? C'est dans ce type de situations que les scripted fields de Kibana entrent en scène.

Les scripted fields sont apparus dès le début de Kibana 4. Lors de leur lancement, ils ne pouvaient être définis que via des Lucene Expressions, un langage de script dans Elasticsearch qui utilise exclusivement des valeurs numériques. La puissance des scripted fields se limitait donc à un sous-ensemble de cas d'utilisation. Dans la version 5.0, Elasticsearch a ajouté Painless, un langage de script puissant et sécurisé qui fonctionne avec différents types de données. Les scripted fields de Kibana 5.0 sont donc bien plus puissants.

Dans la suite de cet article, nous vous montrerons comment créer des scripted fields pour les cas d'utilisation courants. Nous nous appuierons pour cela sur un ensemble de données du tutoriel Getting Started with Kibana et utiliserons une instance d'Elasticsearch et de Kibana exécutée dans Elastic Cloud, que vous pouvez faire tourner gratuitement.

La vidéo suivante vous indique comment lancer une instance personnelle d'Elasticsearch et de Kibana dans Elastic Cloud et y charger un ensemble de données d'exemple.

Fonctionnement des scripted fields

Elasticsearch vous permet de spécifier des scripted fields à chaque requête. Kibana améliore cela en vous permettant de définir un scripted field une fois pour toutes dans la section Management. Il peut ensuite être utilisé à différents endroits de l'interface utilisateur. Veuillez noter que tant que Kibana enregistre les scripted fields et ses autres configurations dans l'index .kibana, cette configuration est spécifique à Kibana et les scripted fields de Kibana ne seront pas exposés aux utilisateurs de l'API d'Elasticsearch.

Lorsque vous définirez un scripted field dans Kibana, vous devrez choisir le langage du script dans la liste des langages installés sur les nœuds d'Elasticsearch qui ont l'option de dynamic scripting activé. « expression » et « painless » sont activés par défaut dans la version 5.0, tandis que seule « expression » est activée dans les versions 2.x. Vous pouvez installer d'autres langages de script et activer le dynamic scripting pour ceux-ci, mais cette opération est déconseillée parce que les scripts ne peuvent pas être suffisamment isolés et ont donc été dépréciés.

Les scripted fields traitent un seul document Elasticsearch à la fois, mais peuvent référencer plusieurs champs dans ce document. Il est donc approprié d'utiliser des scripted fields pour combiner ou transformer les champs d'un document, mais pas pour effectuer des calculs à partir de plusieurs documents (par exemple des calculs sur des données temporelles). Lucene Expressions et Painless fonctionnent avec les champs stockés dans doc_values. Pour les chaînes de caractères, vous devrez donc enregistrer la chaîne dans un type de données keyword. Les scripted fields basés sur Painless ne peuvent pas fonctionner directement sur _source.

Une fois les scripted fields définis dans « Management », l'utilisateur peut interagir avec eux de la même manière qu'avec d'autres champs de Kibana. Les scripted fields s'affichent automatiquement dans la liste des champs de « Discover » et sont disponibles dans « Visualize » pour pouvoir créer des visualisations. Kibana transmet simplement les définitions des scripted fields à Elasticsearch lors des requêtes pour l'évaluation. L'ensemble de données obtenu est combiné à d'autres résultats provenant d'Elasticsearch et présenté à l'utilisateur sous la forme d'un tableau ou d'un graphique.

Certaines limitations des scripted fields sont connues à l'heure où nous écrivons ces lignes. Vous pouvez appliquer aux scripted fields la plupart des agrégations Elasticsearch disponibles dans le constructeur visuel de Kibana, à l'exception notable de l'agrégation « significant terms ». Vous pouvez également filtrer les scripted fields via la barre de filtrage dans Discover, Visualize et Dashboard, même si vous devez prendre garde à écrire des scripts corrects, qui renvoient des valeurs bien définies comme indiqué ci-dessous. Veuillez également vous reporter à la section Bonnes Pratiques ci-dessous pour vous assurer de ne pas déstabiliser l'environnement lorsque vous utilisez des scripted fields.

La vidéo suivante vous montre comment utiliser Kibana pour créer des scripted fields.

Exemples de scripted fields

Cette section présente quelques exemples de scripted fields Lucene Expressions et Painless dans des scénarios Kibana courants. Comme mentionné plus haut, ces exemples ont été développés à partir d'un ensemble de données extrait du tutoriel Getting started with Kibana et supposent que vous utilisez Elasticsearch et Kibana 5.1.1 puisque les versions précédentes présentent des problèmes connus avec le filtrage et le tri de certains types de scripted fields.

La plupart des scripted fields devraient fonctionner directement puisque Lucene Expressions et Painless sont activés par défaut dans Elasticsearch 5.0. La seule exception concerne les scripts qui nécessitent l'analyse grammaticale des champs basée sur regex pour lesquels vous devrez définir les paramètres suivants dans elasticsearch.yml afin d'activer la correspondance regex pour Painless : script.painless.regex.enabled: true

Effectuer des calculs sur un seul champ

  • Exemple : convertir des octets en kilooctets
  • Langage : expressions
  • Type renvoyé : nombre
 doc['bytes'].value / 1024

Remarque : N'oubliez pas que les scripted fields de Kibana fonctionnent sur un document à un instant donné. Il est donc impossible d'effectuer des calculs avec des données temporelles dans un scripted field.

Calcul à partir d'une date qui renvoie un nombre

  • Exemple :  convertir une date en heure du jour
  • Langage : expressions
  • Type retourné : nombre

Lucene Expressions offre un grand nombre de fonctions de manipulation de dates prêtes à l'emploi. Cependant, comme Lucene Expressions ne renvoie que des valeurs numériques, nous devrons utiliser Painless pour renvoyer un jour de la semaine au format chaîne de caractère (ci-dessous).

 doc['@timestamp'].date.hourOfDay

Remarque : le script ci-dessus renvoie le résultat : 1-24

doc['@timestamp'].date.dayOfWeek

Remarque : le script ci-dessus renvoie le résultat : 1-7

Associer deux valeurs de chaînes de caractères

  • Exemple : associer la source et la destination ou le nom et le prénom
  • Langage : painless
  • Type retourné : chaînes de caractères
 doc['geo.dest.keyword'].value + ':' + doc['geo.src.keyword'].value

Remarque : Comme les scripted fields doivent fonctionner sur les champs dans doc_values, nous utilisons les versions .keyword des chaînes de caractères ci-dessus.

Introduire de la logique

  • Exemple : renvoyer un message « téléchargement volumineux » pour tout document de plus de 10 000 octets
  • Langage : painless
  • Type retourné : chaînes de caractères
 if (doc['bytes'].value > 10000) { 
    return "big download";
}
return "";

Remarque : Si vous introduisez de la logique, assurez-vous que chaque chemin d'exécution dispose d'une déclaration et d'une valeur de retour (sauf null). Par exemple, le scripted field ci-dessus échouera avec une erreur de compilation s’il est utilisé dans les filtres de Kibana sans la déclaration de retour à la fin ou si la déclaration renvoie null. Souvenez-vous que l'ajout de logique dans des fonctions n'est pas pris en charge par les scripted fields de Kibana.

Renvoi d'une partie d'une chaîne de caractères

  • Exemple : envoyer la partie de la chaîne de caractères située après le dernier slash d’une URL
  • Langage : painless
  • Type retourné : chaînes de caractères
 def path = doc['url.keyword'].value;
if (path != null) {
    int lastSlashIndex = path.lastIndexOf('/');
    if (lastSlashIndex > 0) {
    return path.substring(lastSlashIndex+1);
    }
}
return "";

Remarque : Évitez dans la mesure du possible d'utiliser des expressions regex pour extraire des parties de chaînes de caractères puisque les opérations indexOf() sollicitent moins les ressources et génèrent moins d'erreurs.

Faire correspondre une chaîne de caractères à l'aide de regex et entreprendre une action suite à une correspondance

  • Exemple : renvoyer le mot « error » si une partie d'une chaîne de caractères « error » est détectée dans le champ « referer » ou sinon, renvoyer la chaîne de caractères « no error »
  • Langage : painless
  • Type retourné : chaînes de caractères
if (doc['referer.keyword'].value =~ /error/) { 
return "error"
} else {
return "no error"
}

Remarque : La syntaxe regex simplifiée est utile pour les expressions conditionnelles basées sur une correspondance regex.

Faire correspondre une chaîne de caractère et renvoyer cette correspondance

  • Exemple : renvoyer le domaine, la chaîne de caractères après le dernier point dans le champ « host »
  • Langage : painless
  • Type retourné : chaînes de caractères
def m = /^.*\.([a-z]+)$/.matcher(doc['host.keyword'].value);
if ( m.matches() ) {
   return m.group(1)
} else {
   return "no match"
}

Remarque : En définissant un objet via les fonctions regex matcher() vous pouvez extraire des groupes de caractères qui correspondent au regex et les renvoyer.

Faire correspondre un nombre et renvoyer cette correspondance

  • Exemple : renvoyer le premier octet de l'adresse IP (enregistrée en tant que chaîne de caractères) et traiter ce dernier comme un nombre
  • Langage : painless
  • Type retourné : nombre
 def m = /^([0-9]+)\..*$/.matcher(doc['clientip.keyword'].value);
if ( m.matches() ) {
   return Integer.parseInt(m.group(1))
} else {
   return 0
}

Remarque : Il est important de renvoyer le type de données adéquat dans un script. La correspondance regex renvoie une chaîne de caractères, même si elle tombe sur un nombre. Vous devez alors la convertir explicitement en nombre entier lors du résultat.

Calcul à partir d'une date qui renvoie des chaînes de caractères

  • Exemple : convertir une date en jour de la semaine au format chaîne de caractères
  • Langage : painless
  • Type retourné : chaîne de caractères
LocalDateTime.ofInstant(Instant.ofEpochMilli(doc['@timestamp'].value), ZoneId.of('Z')).getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault())

Remarque : Comme Painless prend en charge tous les types natifs de Java, il permet d'accéder aux fonctions natives pour ces types, notamment LocalDateTime(), utile pour effectuer des calculs plus complexes avec des dates.

Bonnes pratiques

Comme vous pouvez le constater, le langage Painless permet d'extraire très efficacement des informations utiles de champs arbitraires stockés dans Elasticsearch via les scripted fields de Kibana. Cependant, cette formidable puissance va de pair avec de lourdes responsabilités. 

Vous trouverez ci-dessous quelques-unes des bonnes pratiques pour l'utilisation des scripted fields de Kibana.

  • Veiller à toujours utiliser un environnement de développement pour faire des essais avec les scripted fields. Les scripted fields sont immédiatement actifs après leur sauvegarde dans la section Management de Kibana (ils apparaissent par exemple sur l'écran Discover au niveau du modèle d'index pour tous les utilisateurs). Il est donc déconseillé de développer des scripted fields directement dans l'environnement de production. Nous vous recommandons de tester au préalable votre syntaxe dans un environnement de développement, d'évaluer l'impact des scripted fields sur des ensembles de données réalistes et des volumes de données en pré-production avant de les déployer en production. 
  • Une fois que vous vous êtes assuré que le scripted field sera utile pour les utilisateurs, vous pouvez penser à modifier votre ingestion afin d'extraire le champ au moment de l'indexation de nouvelles données. Le traitement Elasticsearch sera ainsi moins lourd lors de la requête et les utilisateurs de Kibana bénéficieront de résultats plus rapides. Vous pouvez également utiliser l’API _reindex dans Elasticsearch pour indexer à nouveau les données existantes.