Module 2 — ELK Stack : centraliser et interroger des journaux
Bonne lecture et bon apprentissage ! Junior TSAFACK – 12/09/2026 Temps de lecture estimé : 9 minutes
Le problème que l’ELK Stack résout
Section titled “Le problème que l’ELK Stack résout”Le Module 1 a montré comment collecter des métriques (des nombres agrégés dans le temps). Les journaux (logs) sont différents : du texte libre, structuré ou non, dont on a besoin de rechercher le contenu — « montre-moi toutes les requêtes en erreur qui mentionnent /api/paiement dans les 10 dernières minutes ». C’est le rôle d’Elasticsearch (stockage et recherche), généralement couplé à Kibana (visualisation) — les deux composants démontrés dans ce module (le “L” de Logstash, la brique de collecte/transformation, est ici remplacé par un script Python qui indexe directement, pour rester focalisé sur les concepts de recherche).
Indexer des journaux réels
Section titled “Indexer des journaux réels”import requests
def generer_log(): return { "@timestamp": datetime.datetime.utcnow().isoformat(), "level": random.choice(["INFO", "INFO", "INFO", "WARNING", "ERROR"]), "endpoint": random.choice(["/api/login", "/api/panier", "/api/paiement", "/api/produits"]), "duration_ms": round(random.uniform(5, 400), 1), }
for _ in range(200): requests.post("http://localhost:9200/app-logs/_doc", json=generer_log())$ python ingest_logs.pyDocuments indexés : 200Vérifié directement auprès d’Elasticsearch :
$ curl -s http://localhost:9200/app-logs/_count{"count":200}Un vrai piège rencontré : une recherche exacte qui renvoie 0 résultat
Section titled “Un vrai piège rencontré : une recherche exacte qui renvoie 0 résultat”En cherchant tous les journaux de niveau ERROR avec une requête term (recherche exacte, sans analyse du texte) :
$ curl -X POST http://localhost:9200/app-logs/_search -d '{"query":{"term":{"level":"ERROR"}}}'{"hits":{"total":{"value":0}}}Zéro résultat, alors que le script en a bien indexé environ un cinquième sur 200. En essayant une requête match (recherche « plein texte », qui analyse la requête de la même façon que le champ) sur le même champ :
$ curl -X POST http://localhost:9200/app-logs/_search -d '{"query":{"match":{"level":"ERROR"}}}'{"hits":{"total":{"value":33}}}33 résultats. La différence entre les deux requêtes a révélé la cause exacte : le mapping (schéma) que le champ level a reçu automatiquement.
$ curl http://localhost:9200/app-logs/_mapping"level": { "type": "text", "fields": { "keyword": { "type": "keyword" } }}Explication : en l’absence de mapping explicite déclaré à l’avance, Elasticsearch devine le type de chaque champ texte à partir du premier document indexé, et lui donne par défaut le type text — un champ analysé : à l’indexation, "ERROR" est découpé en tokens et mis en minuscules ("error"), pour permettre une recherche plein texte insensible à la casse. Une requête term, elle, ne fait aucune analyse : elle compare la valeur donnée ("ERROR", en majuscules) directement aux tokens stockés ("error", en minuscules) — la correspondance exacte échoue. Une requête match, à l’inverse, analyse aussi la valeur recherchée avant de comparer — "ERROR" devient "error" des deux côtés, la comparaison réussit.
Elasticsearch anticipe ce genre de confusion : il crée automatiquement, à côté du champ text, un sous-champ level.keyword (non analysé, comparaison strictement exacte) — une requête term sur level.keyword fonctionne correctement :
$ curl -X POST http://localhost:9200/app-logs/_search -d '{"query":{"term":{"level.keyword":"ERROR"}}}'{"hits":{"total":{"value":33}}}La règle à retenir : utiliser match pour une recherche plein texte (l’utilisateur tape “erreur paiement”, peu importe la casse ou l’ordre des mots) ; utiliser term sur le sous-champ .keyword pour une comparaison exacte (un statut, un code, un identifiant) — confondre les deux est l’une des causes les plus fréquentes de « ma recherche ne trouve rien alors que la donnée est bien là ».
Agréger : compter par catégorie
Section titled “Agréger : compter par catégorie”$ curl -X POST http://localhost:9200/app-logs/_search -d '{"size":0,"aggs":{"par_niveau":{"terms":{"field":"level.keyword"}}}}'
"buckets": [ {"key": "INFO", "doc_count": 123}, {"key": "WARNING", "doc_count": 44}, {"key": "ERROR", "doc_count": 33}]123 + 44 + 33 = 200 — la totalité des documents indexés, répartie exactement par niveau. C’est ce type d’agrégation, affiché comme un graphique dans Kibana, qui permet de repérer en un coup d’œil un pic anormal d’erreurs sur un endpoint donné, sans avoir à parcourir manuellement des milliers de lignes de log.
ELK dans une stack d’observabilité complète
Section titled “ELK dans une stack d’observabilité complète”| Brique | Rôle |
|---|---|
| Métriques (Prometheus/Grafana) | « Combien de requêtes par seconde ? Quelle latence ? » — des séries temporelles de nombres |
| Logs (ELK, ce module) | « Que s’est-il passé exactement, en détail, sur cette requête précise ? » — du texte structuré, cherchable |
| Traces (hors périmètre de ce cours — Jaeger, Tempo) | « Ce ralentissement vient de quel service, dans une chaîne d’appels entre microservices ? » |
Les trois piliers de l’observabilité répondent à des questions différentes et complémentaires — une stack de production mature les combine, plutôt que de choisir l’un au détriment des autres.