25 - Debugger un pipeline Logstash
Ce que tu vas apprendre
- Une methodologie systématique pour debugger un pipeline
- Utiliser stdout a des points strategiques
- Tracer l'exécution avec des tags et des champs @metadata
- Changer le log level a chaud
- Les erreurs les plus courantes et leurs solutions
Prerequisites
- Avoir un pipeline fonctionnel (ou un qui ne fonctionne pas, c'est le sujet)
La methodologie : du général au spécifique
Quand un pipeline ne fait pas ce qu'on attend, la tentation est de regarder le code pendant 30 minutes en esperant voir l'erreur. Ca ne marche jamais. Ma méthode :
- Les logs Logstash : est-ce que Logstash signale une erreur ?
- Les événements arrivent-ils ? : stdout au début du pipeline
- Le parsing fonctionne-t-il ? : stdout apres les filtres
- L'output recoit-il les donnees ? : vérifier Elasticsearch
- Les champs sont-ils corrects ? : inspecter un événement en détail
Étape 1 : les logs Logstash
bashdocker compose logs logstash --tail 50
Les erreurs courantes apparaissent ici : syntaxe .conf invalide, plugin manquant, Elasticsearch injoignable. Si Logstash ne démarré pas, la réponse est dans les logs.
Augmente le log level pour plus de détail :
yaml# logstash.yml
log.level: debug
Ou a chaud via l'API (sans redemarrage) :
bashcurl -X PUT "http://localhost:9600/_node/logging?pretty" \
-H "Content-Type: application/json" \
-d '{"logger.logstash.outputs.elasticsearch": "DEBUG"}'
Ca active le debug uniquement pour l'output Elasticsearch. Moins de bruit que log.level: debug global.
Pour revenir au niveau normal :
bashcurl -X PUT "http://localhost:9600/_node/logging?pretty" \
-H "Content-Type: application/json" \
-d '{"logger.logstash.outputs.elasticsearch": "INFO"}'
Étape 2 : stdout comme sonde
Place un stdout temporaire a l'endroit ou tu suspectes le problème :
input {
beats { port => 5044 }
}
filter {
# Sonde 1 : voir l'evenement brut
stdout { codec => rubydebug }
grok {
match => { "message" => "%{COMBINEDAPACHELOG}" }
}
# Sonde 2 : voir l'evenement apres Grok
stdout { codec => rubydebug }
mutate {
convert => { "response" => "integer" }
}
}
output {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "logs-%{+YYYY.MM.dd}"
}
}
Oui, tu peux mettre stdout dans le bloc filter. C'est un output, mais Logstash l'accepte a cet endroit pour le debug. Ca affiche l'événement a ce point precis du pipeline.
stdout conditionnel
Pour ne pas noyer la console, filtre les événements affiches :
filter {
grok {
match => { "message" => "%{COMBINEDAPACHELOG}" }
}
# Afficher uniquement les evenements en erreur
if "_grokparsefailure" in [tags] {
stdout { codec => rubydebug }
}
}
Étape 3 : tracer avec des tags
Ajoute des tags a chaque étape pour voir par ou l'événement est passe :
filter {
mutate { add_tag => ["step_1_raw"] }
grok {
match => { "message" => "%{COMBINEDAPACHELOG}" }
add_tag => ["step_2_grok_ok"]
tag_on_failure => ["step_2_grok_fail"]
}
if "step_2_grok_ok" in [tags] {
date {
match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
add_tag => ["step_3_date_ok"]
tag_on_failure => ["step_3_date_fail"]
}
}
geoip {
source => "clientip"
add_tag => ["step_4_geoip_ok"]
tag_on_failure => ["step_4_geoip_fail"]
}
}
output {
stdout { codec => rubydebug }
}
Dans la sortie, regarde le tableau tags :
"tags" => ["step_1_raw", "step_2_grok_ok", "step_3_date_ok", "step_4_geoip_fail"]
Tu vois immédiatement que GeoIP a echoue. Peut-etre une IP privee, un champ clientip absent, ou la base GeoLite2 non montee.
N'oublie pas de supprimer les tags de debug avant la mise en production.
Étape 4 : @metadata pour le debug invisible
Si tu veux tracer sans polluer les tags (qui apparaissent dans Elasticsearch) :
filter {
mutate {
add_field => { "[@metadata][debug_after_grok]" => "true" }
}
# Voir les metadata
stdout { codec => rubydebug { metadata => true } }
}
Les champs @metadata n'apparaissent jamais dans Elasticsearch. Tu peux les laisser dans le pipeline sans consequence.
Les erreurs les plus courantes
_grokparsefailure
Le pattern Grok ne matche pas la ligne.
Diagnostic :
filter {
grok {
match => { "message" => "%{COMBINEDAPACHELOG}" }
tag_on_failure => ["_grokparsefailure"]
}
if "_grokparsefailure" in [tags] {
# Afficher la ligne qui ne matche pas
ruby {
code => 'logger.warn("Grok failed on: #{event.get("message")}")'
}
}
}
La ligne qui echoue apparaît dans les logs Logstash. Copie-la dans le Grok Debugger et ajuste le pattern.
Causes frequentes : nouveau format de log, espaces supplementaires, caractères speciaux, encodage UTF-8 avec BOM.
_dateparsefailure
Le format de date ne correspond pas.
Diagnostic : affiche la valeur du champ avec stdout et compare au format défini.
Causes frequentes : timezone manquante, format yyyy vs yy, millisecondes absentes, espaces en début/fin de champ.
Mapping conflict dans Elasticsearch
L'événement est rejete par Elasticsearch.
Diagnostic :
bashdocker compose logs logstash | grep "mapper_parsing_exception"
Causes frequentes : un champ status envoye comme string puis comme integer, ou inversement. Definis un index template explicite (voir article 17).
Pipeline ne charge pas
bashdocker compose logs logstash | grep -i "error\|failed\|exception"
Causes frequentes : syntaxe .conf invalide (virgule en trop, accolade manquante), path.config incorrect dans pipelines.yml, fichier .conf non monte dans Docker.
Événements perdus
events.in augmente mais events.out stagne.
Diagnostic : vérifié si un drop {} ou event.cancel supprime des événements. Verifie les tags _grokparsefailure et les conditionnels qui pourraient router les événements vers un output inexistant.
Performance degradee
Diagnostic : API _node/stats et _node/hot_threads (voir articles 21 et 22).
Le fichier .conf minimal pour isoler un problème
Quand rien ne marche, reduis au minimum :
input {
generator {
message => 'ta ligne de log problematique ici'
count => 1
}
}
filter {
grok {
match => { "message" => "ton pattern ici" }
}
}
output {
stdout { codec => rubydebug }
}
Pas d'Elasticsearch, pas de Filebeat, pas de conditions. Juste l'input, le filtre suspect, et stdout. Si ca marche, le problème est ailleurs. Si ca ne marche pas, tu as isole le filtre coupable.
C'est ma technique sur paltemps.fr quand un pipeline en prod se comporte bizarrement. Je copie le filtre suspect dans un pipeline minimal, je teste avec 5 lignes de log, et je trouve le bug en 2 minutes au lieu de 30.
Valider la syntaxe sans démarrer
bashdocker exec logstash bin/logstash --config.test_and_exit \
-f /usr/share/logstash/pipeline/main.conf
Logstash vérifié la syntaxe du fichier et quitte. Pas de JVM a démarrer, pas d'attente. Si la syntaxe est bonne :
Configuration OK
Si elle est mauvaise, le message d'erreur indique la ligne et le type d'erreur.
Résumé
- Methodologie : logs Logstash -> stdout en sonde -> tags de trace -> inspecter les champs
stdout { codec => rubydebug }peut etre place dans le bloc filter pour inspecter a mi-chemin- Les tags de debug tracent par ou passe l'événement,
@metadataest invisible dans l'output - Le log level se change a chaud via l'API (
PUT _node/logging) --config.test_and_exitvalide la syntaxe sans démarrer- Isole le problème dans un pipeline minimal avec
generator+stdout
Precedent : 24 - Sécurité | Suivant : 26 - Testing