Données et bases de données

Centraliser ses logs avec Elasticsearch, Logstash et Kibana

Intermédiaire9 min de lectureMis à jour

Cette page a été traduite de l'anglais avec l'aide de l'IA, puis vérifiée par rapport à l'original. Lire la version anglaise

Les logs ne deviennent utiles que structurés, centralisés et interrogeables quelques secondes après leur écriture. Ce guide parcourt la stack Elastic de bout en bout : émettre des logs JSON, les expédier avec Beats ou Elastic Agent, les parser avec Logstash ou des ingest pipelines, maîtriser les mappings avec des index templates, gérer la rétention avec ILM et dimensionner les shards pour garder un cluster en bonne santé.

Ce que vous allez apprendre

  • Décrire le rôle de chaque composant de la stack Elastic et choisir entre Logstash et les ingest pipelines
  • Émettre des logs JSON structurés et les aligner sur un schéma de champs cohérent
  • Écrire des règles de parsing grok et dissect et enrichir les événements à l'ingestion
  • Maîtriser les mappings avec les index templates et component templates, et la rétention avec l'index lifecycle management
  • Dimensionner shards et index pour que le cluster reste stable quand le volume de logs augmente

Un serveur d'applications unique qui écrit dans un fichier se débogue facilement : on ouvre le fichier, on cherche, c'est réglé. Vingt conteneurs derrière un load balancer, qui redémarrent à chaque déploiement et disparaissent quand on réduit le nombre d'instances, c'est un tout autre problème. Au moment où vous comprenez que quelque chose a mal tourné, le conteneur qui a journalisé la cause n'existe peut-être plus. La centralisation des logs sert précisément à rendre ce scénario impossible : chaque ligne quitte la machine au moment où elle est écrite, est découpée en champs et devient interrogeable sur l'ensemble des services à la fois.

La stack Elastic (Elasticsearch pour le stockage et la recherche, Logstash pour le traitement, Kibana pour l'interrogation et la visualisation, et les agents d'expédition Beats ou Elastic Agent) reste la réponse la plus largement déployée. Ce guide couvre la version majeure actuelle, Elastic 9, et se concentre sur les décisions qui déterminent si la plateforme reste utile et abordable à mesure que le volume augmente.

Le rôle de chaque composant

  • Elasticsearch stocke des documents dans des index et répond aux requêtes. C'est un moteur de recherche distribué bâti sur Apache Lucene ; il partage donc l'index inversé et la chaîne d'analyse décrits dans le guide Apache Solr, que ce guide ne reprendra pas. Ce qui compte ici, c'est qu'il s'agit d'un stockage quasi temps réel : un document devient interrogeable après un refresh, par défaut en moins d'une seconde.
  • Kibana est l'interface de requête et de visualisation, mais aussi la console d'administration des pipelines, des politiques de cycle de vie, des règles d'alerte et des dashboards.
  • Beats sont des agents d'expédition spécialisés écrits en Go : Filebeat pour les fichiers de log et les conteneurs, Metricbeat pour les métriques système et applicatives, Heartbeat pour les contrôles de disponibilité, Packetbeat et Auditbeat pour les données réseau et d'audit.
  • Elastic Agent est l'agent unifié, plus récent. Un seul processus gère logs, métriques et données de sécurité, et il télécharge des intégrations (configuration d'entrée, ingest pipelines, index templates et dashboards préconstruits, le tout packagé) depuis un serveur Fleet central. Pour un nouveau déploiement, c'est la voie par défaut ; les Beats restent pleinement supportés et sont plus simples quand vous n'avez besoin que d'une seule chose.
  • Logstash est un pipeline de traitement autonome composé d'entrées, de filtres et de sorties. Il met en mémoire tampon, transforme et route, et sait lire des sources qu'aucun autre composant ne sait lire.

La stack ne représente que la moitié du tableau. Logs, métriques et traces sont trois vues d'un même système, et une application qui expose des endpoints de santé et de métriques, comme décrit dans Actuator et observabilité, vous fournit les deux autres.

Journaliser dès la source pour une lecture par machine

Le changement le plus rentable de tout projet de logging intervient avant toute expédition : faire émettre à l'application des événements structurés plutôt que de la prose.

{
  "@timestamp": "2026-09-07T08:14:22.481Z",
  "log.level": "ERROR",
  "service.name": "payment-api",
  "service.version": "4.2.1",
  "trace.id": "3f1c0a9d5e6b47c8",
  "transaction.id": "9b2e77a1",
  "event.dataset": "payment.settlement",
  "http.request.method": "POST",
  "url.path": "/v1/settlements",
  "http.response.status_code": 502,
  "message": "Settlement rejected by acquirer",
  "error.type": "AcquirerTimeoutException",
  "labels": { "merchant_id": "M-88213" }
}

Chaque champ peut être interrogé, agrégé et mis en graphique sans parser intermédiaire. Les applications Java obtiennent ce résultat avec un encodeur JSON Logback ou Log4j2 ; les encodeurs compatibles ECS émettent directement les noms de champs ci-dessus. Quand un document semble incorrect, le coller dans un formateur JSON fait immédiatement ressortir un niveau d'imbrication en trop ou une clé dupliquée.

D'où la deuxième décision : adopter une convention de nommage des champs et s'y tenir. L'Elastic Common Schema définit des noms pour les notions que tout système journalise (host.name, service.name, event.duration, error.stack_trace, user.id) ; l'utiliser, c'est garantir que les dashboards et règles de détection préconstruits fonctionnent sur vos données et que les logs de deux équipes peuvent être corrélés. Inventez vos propres noms, et chaque dashboard devient du sur-mesure.

Deux règles méritent de figurer dans vos standards de code. Ne journalisez jamais de secrets, de numéros de carte ni de données personnelles que vous ne mettriez pas en base, car une plateforme de logs est généralement moins protégée que la base de données. Et placez les identifiants de corrélation (trace id, request id, tenant id) dans un mapped diagnostic context pour que chaque ligne les porte automatiquement.

Expédition et parsing

Filebeat suit les fichiers en continu, mémorise sa position de lecture dans un registre pour qu'un redémarrage ne duplique ni ne perde de données, gère les stack traces multilignes et applique de la backpressure quand la destination ralentit.

filebeat.inputs:
  - type: filestream
    id: payment-api
    paths: ["/var/log/payment-api/*.json"]
    parsers:
      - ndjson:
          target: ""
          overwrite_keys: true
          add_error_key: true

processors:
  - add_host_metadata: ~
  - add_kubernetes_metadata: ~
  - drop_event:
      when:
        equals:
          log.level: "TRACE"

output.elasticsearch:
  hosts: ["https://es01:9200"]
  api_key: "${ES_API_KEY}"

Les conteneurs changent le modèle d'expédition, pas le principe : écrivez sur stdout, laissez le runtime collecter, et faites tourner un agent par nœud avec un mécanisme d'autodiscovery qui rattache les labels du pod, du namespace et du conteneur. Voir Les fondamentaux de Docker pour comprendre pourquoi journaliser sur stdout est le choix natif des conteneurs.

Pour les sources que vous ne pouvez pas modifier (un flux syslog de pare-feu, une appliance qui émet des lignes à largeur fixe), le parsing est inévitable. Deux outils s'en chargent.

Dissect découpe selon des délimiteurs fixes. Il est nettement plus rapide qu'une expression régulière et doit être votre premier choix quand le format est stable :

%{clientip} - %{user} [%{timestamp}] "%{method} %{path} %{httpversion}" %{status} %{bytes}

Grok est une bibliothèque d'expressions régulières nommées et gère les entrées réellement irrégulières :

%{IPORHOST:source.ip} %{USER:user.name} \[%{HTTPDATE:event.created}\] "%{WORD:http.request.method} %{URIPATHPARAM:url.original}" %{NUMBER:http.response.status_code:int}

Les patterns grok sont aussi la cause classique des ingestions qui s'effondrent : un pattern non ancré qui fait du backtracking catastrophique sature un cœur de CPU. Ancrez vos patterns, préférez dissect quand c'est possible et testez dans le Grok Debugger de Kibana avant de déployer.

Reste à choisir où le parsing s'exécute. Un ingest pipeline Elasticsearch tourne dans le cluster, sur les nœuds ayant le rôle ingest, et ne demande aucun service supplémentaire :

PUT _ingest/pipeline/payment-logs
{
  "processors": [
    { "dissect": { "field": "message", "pattern": "%{ts} %{level} [%{thread}] %{logger} - %{msg}" } },
    { "date":    { "field": "ts", "formats": ["ISO8601"], "target_field": "@timestamp" } },
    { "geoip":   { "field": "source.ip", "target_field": "source.geo", "ignore_missing": true } },
    { "remove":  { "field": ["ts", "message"], "ignore_missing": true } }
  ],
  "on_failure": [
    { "set": { "field": "event.kind", "value": "pipeline_error" } },
    { "set": { "field": "error.message", "value": "{{ _ingest.on_failure_message }}" } }
  ]
}

Remarquez le bloc on_failure. Sans lui, une ligne mal formée fait rejeter le document et vous perdez précisément l'événement que vous vouliez le plus voir. Marquez les échecs et indexez-les malgré tout.

Logstash vaut le service supplémentaire quand il vous faut un tampon durable entre des producteurs bavards et le cluster (sa persistent queue survit aux redémarrages), quand les événements doivent partir vers plusieurs destinations, ou quand la transformation est assez lourde pour que vous préfériez ne pas y consacrer le CPU des nœuds de données. Une architecture de production courante : Beats vers Kafka pour le buffering, Logstash qui consomme depuis Kafka, et Elasticsearch comme destination finale.

Mappings, data streams et rétention

Elasticsearch devine un mapping pour tout champ qu'il n'a encore jamais vu. Cette commodité engendre deux problèmes chroniques : un champ arrivé d'abord comme nombre puis comme chaîne est rejeté, et une explosion de mapping (des milliers de champs créés dynamiquement à partir de clés non bornées comme labels.<request-id>) gonfle le cluster state jusqu'à rendre le cluster instable.

On garde le contrôle avec des component templates assemblés dans un index template :

PUT _index_template/logs-payment
{
  "index_patterns": ["logs-payment-*"],
  "data_stream": {},
  "priority": 200,
  "composed_of": ["logs-mappings", "logs-settings"],
  "template": {
    "settings": { "index.number_of_shards": 2, "index.number_of_replicas": 1,
                  "index.lifecycle.name": "logs-90d" },
    "mappings": {
      "dynamic": "strict",
      "properties": {
        "@timestamp":  { "type": "date" },
        "service.name":{ "type": "keyword" },
        "log.level":   { "type": "keyword" },
        "message":     { "type": "text" },
        "http.response.status_code": { "type": "integer" }
      }
    }
  }
}

La distinction entre keyword et text est celle à bien assimiler : keyword stocke la valeur complète pour le filtrage exact, l'agrégation et le tri ; text est analysé en tokens pour la recherche plein texte et ne s'agrège pas efficacement. Noms de services, niveaux de log et noms d'hôtes sont des keywords. Les messages en texte libre sont du text.

Un data stream est la forme moderne pour les séries temporelles en ajout seul. Vous écrivez vers un nom unique, logs-payment-prod, et Elasticsearch gère derrière lui une série d'index sous-jacents cachés, en basculant vers un nouveau (rollover) dès qu'un seuil de taille ou d'âge est atteint. Le rollover compte, car supprimer d'anciennes données revient alors à supprimer un index entier, ce qui ne coûte presque rien, alors que supprimer des documents par requête est coûteux.

L'index lifecycle management automatise tout le cycle de vie de ces données :

PUT _ilm/policy/logs-90d
{
  "policy": { "phases": {
    "hot":    { "actions": { "rollover": { "max_primary_shard_size": "40gb", "max_age": "1d" } } },
    "warm":   { "min_age": "3d",  "actions": { "forcemerge": { "max_num_segments": 1 },
                                               "shrink": { "number_of_shards": 1 } } },
    "cold":   { "min_age": "15d", "actions": { "set_priority": { "priority": 0 } } },
    "delete": { "min_age": "90d", "actions": { "delete": {} } }
  }}
}

Les nœuds hot absorbent les écritures sur des disques rapides ; les nœuds warm conservent les données récentes rarement écrites ; les nœuds cold gardent ce qui n'est interrogé qu'occasionnellement. Fixez la rétention par jeu de données (événements d'audit et logs de debug justifient rarement la même fenêtre) et gardez en tête que le gigaoctet le moins cher est celui que vous n'avez jamais indexé. Écarter les événements TRACE et les requêtes de health check dès l'agent d'expédition divise souvent le volume par deux sans rien perdre pour l'investigation.

Requêtes et dashboards

Dans la vue Discover de Kibana, KQL couvre l'essentiel du filtrage quotidien : service.name:"payment-api" and http.response.status_code >= 500 and not url.path:"/health". L'ancienne syntaxe Lucene reste disponible pour les expressions régulières et la recherche approximative (fuzzy).

ES|QL, le langage de requête à base de pipes d'Elastic, est le changement le plus marquant de la génération actuelle. Il exprime filtrage, transformation et agrégation en une seule chaîne lisible :

FROM logs-payment-*
| WHERE http.response.status_code >= 500 AND @timestamp > NOW() - 1 hour
| STATS failures = COUNT(*) BY service.name, error.type
| SORT failures DESC
| LIMIT 20

Construisez vos dashboards autour de questions plutôt qu'autour des champs disponibles : taux d'erreur par service, percentiles de latence, endpoints en échec les plus fréquents, marqueurs de déploiement. Les règles d'alerte de Kibana transforment n'importe lequel de ces indicateurs en notification dès qu'un seuil est franchi. Et servez-vous des runtime fields pour l'attribut occasionnel que vous avez oublié d'extraire : calculés au moment de la requête, ils ne coûtent rien tant qu'on ne les utilise pas et vous épargnent une réindexation.

Dimensionnement et exploitation

La plupart des clusters de logs en mauvaise santé souffrent du même mal : trop de shards. Chaque shard est un index Lucene avec son surcoût en mémoire et en descripteurs de fichiers, et le cluster state qui les recense doit être répliqué sur chaque nœud.

  • Visez 10 à 50 Go par shard, et environ 20 shards au plus par gigaoctet de heap sur un nœud.
  • Réglez le heap de la JVM à environ la moitié de la RAM de la machine, sans jamais dépasser ~31 Go, afin que les pointeurs restent compressés ; le reste de la RAM alimente le page cache dont Lucene dépend.
  • Séparez les rôles des nœuds quand le cluster grandit : des nœuds master-eligible dédiés (trois, pour le quorum) maintiennent la stabilité du cluster state quand les nœuds de données sont chargés.
  • Surveillez les disk watermarks. À 85 %, Elasticsearch cesse d'allouer de nouveaux shards à un nœud ; à 95 %, il passe les index en lecture seule. Les deux prennent les équipes par surprise à trois heures du matin.
  • Faites des snapshots vers un stockage objet. La réplication protège contre la perte d'un nœud, pas contre un ingest pipeline défectueux ni contre une suppression malencontreuse.

Deux alternatives méritent d'être citées honnêtement. OpenSearch est un fork sous licence Apache, doté d'une API compatible et de ses propres dashboards ; c'est le choix naturel quand la licence Elastic pose problème. Grafana Loki fait le pari architectural inverse : n'indexer qu'un petit ensemble de labels, conserver le contenu brut des logs compressé dans un stockage objet et faire l'équivalent d'un grep au moment de la requête. C'est beaucoup moins cher à gros volume et beaucoup plus faible en recherche plein texte sur l'ensemble des données. Si vos requêtes sont toujours du type « ce service, cette fenêtre de temps », Loki convient ; si elles sont du type « trouver ce token n'importe où », il ne convient pas. Quand le volume de logs dépasse ce qu'un cluster de recherche devrait raisonnablement contenir, les modèles de stockage et de traitement présentés dans le guide d'architecture big data prennent le relais.

Points clés à retenir

  • Des logs JSON structurés suivant un schéma cohérent comme ECS suppriment le parsing du pipeline et rendent chaque champ interrogeable et exploitable en graphique.
  • Elastic Agent avec les intégrations Fleet est l'agent par défaut des nouveaux déploiements ; les Beats restent simples, et Logstash se justifie pour le buffering, la diffusion multi-destinations et les transformations lourdes.
  • Préférez dissect à grok quand le format est fixe, ancrez vos patterns grok et ajoutez toujours un handler on_failure pour que les lignes invalides soient marquées plutôt que perdues.
  • Maîtrisez les mappings avec des index templates et des component templates, distinguez keyword de text, et prévenez les explosions de mapping dues à des noms de champs non bornés.
  • Écrivez dans des data streams, faites un rollover sur la taille ou l'âge, et exprimez la rétention sous forme de politique ILM avec des phases hot, warm, cold et delete par jeu de données.
  • Gardez des shards entre 10 et 50 Go, un heap inférieur à environ 31 Go, surveillez les disk watermarks et faites des snapshots vers un stockage objet, car les répliques ne sont pas des sauvegardes.

Sources et lectures complémentaires

Questions fréquentes

Ai-je encore besoin de Logstash maintenant que les ingest pipelines existent ?
Souvent non. Les ingest pipelines d'Elasticsearch prennent en charge le parsing, l'enrichissement et l'extraction des dates au sein même du cluster, et les intégrations Elastic Agent sont livrées avec des pipelines déjà écrits. Logstash se justifie quand il vous faut un buffer en amont du cluster, une traduction de protocole pour des sources que Beats ne sait pas lire, une diffusion vers plusieurs destinations, ou des transformations lourdes que vous préférez ne pas faire tourner sur les nœuds de données.
Combien de temps faut-il conserver les logs ?
Assez longtemps pour mener les investigations que vous menez réellement, plus ce qu'exige la conformité : pour la plupart des équipes, quelques jours de données chaudes interrogeables et des semaines ou des mois de stockage plus froid. Traduisez cette réponse en politique ILM plutôt qu'en script de suppression, et fixez la rétention par type de données : logs de requêtes, traces d'audit et traces de debug méritent rarement la même fenêtre.
Pourquoi mon cluster est-il jaune ou rouge ?
Jaune signifie que certains shards répliques ne sont pas assignés : les données sont intactes mais plus redondées, ce qui est courant sur un cluster de développement à un seul nœud où les répliques ne peuvent jamais être allouées. Rouge signifie qu'un shard primaire n'est pas assigné et qu'une partie des données est indisponible. L'API allocation explain donne la raison exacte, le plus souvent les disk watermarks, un nœud qui a quitté le cluster ou trop peu de nœuds pour le nombre de répliques demandé.
La stack Elastic est-elle la seule option pour les logs ?
Non. OpenSearch est un fork d'Elasticsearch sous licence Apache, doté d'une API compatible et de son propre outil de dashboards, et Grafana Loki adopte une approche différente : il n'indexe que des labels et stocke les lignes de log brutes dans un stockage objet, ce qui coûte bien moins cher à gros volume mais se prête mal à la recherche plein texte. Choisissez selon vos schémas de requêtes, le coût par gigaoctet et l'outillage que votre équipe exploite déjà.