10 - Le filtre Dissect : parser sans regex
Ce que tu vas apprendre
- La syntaxe Dissect et comment elle fonctionne sans regex
- Parser les memes logs qu'avec Grok, mais plus vite
- Les opérateurs avances : append, padding, key-value
- Comparer les performances Grok vs Dissect
- Quand utiliser Dissect et quand rester sur Grok
Prerequisites
- Avoir compris Grok (voir article 09)
Grok m'a rendu paresseux, puis Dissect m'a reveille
Pendant un an, j'ai tout parse avec Grok. Logs applicatifs, Nginx, syslog, CSV. Grok marchait, je ne me posais pas de questions. Jusqu'au jour ou un pipeline a commence a prendre du retard. 50 000 événements par seconde en entree, Logstash n'en traitait que 30 000. Le bottleneck : Grok.
Le pattern faisait 300 caractères. Chaque événement prenait 0.5 ms a parser. Ca parait rien, mais multiplie par 50 000 et tu as 25 secondes de parsing pour 1 seconde de donnees. Le pipeline ne pouvait pas suivre.
J'ai remplace Grok par Dissect sur ce pipeline. Meme résultat, 10 fois plus rapide. Le pipeline a rattrape son retard en 20 minutes.
Comment Dissect fonctionne
Dissect ne fait pas de regex. Il decoupe le texte en se basant sur des delimiteurs fixes. Tu lui dis "il y a un espace ici, un crochet la, un tiret la-bas" et il decoupe.
filter {
dissect {
mapping => {
"message" => "%{timestamp} %{level} [%{service}] %{method} %{url} %{status} %{duration}ms"
}
}
}
Ligne d'entree :
2026-03-31 14:23:01 ERROR [api-users] POST /users 500 5023ms
Résultat :
json{
"timestamp": "2026-03-31 14:23:01",
"level": "ERROR",
"service": "api-users",
"method": "POST",
"url": "/users",
"status": "500",
"duration": "5023"
}
Dissect a trouver les champs en se basant sur les espaces et les crochets. Pas de regex, pas de backtracking, pas de moteur d'expressions regulieres.
La syntaxe
Capture basique
%{field_name}
Capture tout ce qui se trouve entre le delimiteur précédent et le suivant.
Delimiteurs
Les delimiteurs sont le texte litteral entre les %{}. Dans le mapping "%{a} - %{b}", le delimiteur est - (espace tiret espace).
# Espace comme delimiteur
"%{a} %{b} %{c}"
# Pipe comme delimiteur
"%{a}|%{b}|%{c}"
# Crochets
"[%{a}] [%{b}]"
# Texte fixe
"status=%{status} duration=%{duration}"
Ignorer un champ
Si tu veux consommer du texte sans le capturer, utilise ? :
"%{timestamp} %{level} %{?ignore_this} %{message}"
Le troisieme champ est lu et jette. %{?nom} est un champ "poubelle".
Les opérateurs avances
Append : `+`
Concatene plusieurs captures dans le meme champ :
"%{+timestamp} %{+timestamp} %{level} %{message}"
Entree : 2026-03-31 14:23:01 ERROR timeout
Résultat : timestamp = "2026-03-31 14:23:01"
Les deux premiers %{+timestamp} sont concatenes avec un espace. L'ordre de concatenation suit l'ordre d'apparition.
Tu peux changer l'ordre avec /N :
"%{+timestamp/2} %{+timestamp/1} %{level} %{message}"
Entree : 14:23:01 2026-03-31 ERROR timeout
Résultat : timestamp = "2026-03-31 14:23:01" (l'ordre est inverse grâce à /1 et /2)
Skip padding : `->`
Absorbe les espaces de padding (espaces multiples entre les champs) :
"%{level->} %{message}"
Entree : ERROR Connection timeout (3 espaces entre ERROR et Connection)
Résultat : level = "ERROR", message = "Connection timeout"
Sans ->, Dissect s'arrêté au premier espace et level contient ERROR suivi d'espaces.
C'est courant dans les logs ou le niveau est aligne :
INFO User logged in
WARN Slow query
ERROR Connection refused
-> consomme le padding et donne un level propre dans les trois cas.
Key-value : `?` et `&`
Extrait des paires clé-valeur dynamiques :
"%{?key1}=%{&key1} %{?key2}=%{&key2}"
Entree : user=john status=active
Résultat : john est stocke dans le champ user, active dans le champ status.
%{?key1} capture le nom du champ (et le jette), %{&key1} capture la valeur et la stocke dans un champ qui porte le nom capture par %{?key1}.
C'est puissant, mais si tes paires clé-valeur sont plus complexes (guillemets, nombre variable), le filtre KV est plus adapte (voir article 13).
Grok vs Dissect : comparaison cote a cote
Prenons la meme ligne de log :
192.168.1.10 - john [31/Mar/2026:14:23:01 +0000] "GET /api/users HTTP/1.1" 200 1234
Avec Grok
filter {
grok {
match => { "message" => "%{COMMONAPACHELOG}" }
}
}
Avec Dissect
filter {
dissect {
mapping => {
"message" => '%{clientip} %{ident} %{auth} [%{timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{response} %{bytes}'
}
}
}
Le résultat est identique. Les memes champs, les memes valeurs. Mais Dissect est beaucoup plus rapide.
Performance
| Metrique | Grok | Dissect |
|---|---|---|
| Temps par événement | ~0.5 ms | ~0.05 ms |
| Facteur | 1x | ~10x plus rapide |
| CPU | Eleve (regex engine) | Faible (string split) |
| Mémoire | Compile la regex | Aucune compilation |
Ces chiffres varient selon la complexité du pattern, mais l'ordre de grandeur est correct : Dissect est 5 a 10 fois plus rapide.
Conversion de types avec convert_datatype
Dissect extrait tout en string. Pour convertir :
filter {
dissect {
mapping => {
"message" => "%{timestamp} %{level} %{service} %{status} %{duration}ms"
}
convert_datatype => {
"status" => "int"
"duration" => "int"
}
}
}
C'est intégré dans Dissect : pas besoin d'un filtre mutate séparé.
Les limites de Dissect
Dissect ne peut pas tout faire. Voici ce qui bloque :
Pas de regex. Si le delimiteur n'est pas fixe, Dissect ne peut pas parser. Un nombre variable d'espaces (sans ->) ou un champ optionnel en fin de ligne font échouer Dissect.
Pas de champs optionnels. Si une ligne peut avoir 5 ou 6 champs, Dissect echoue sur les lignes a 5 champs. Grok gere ca avec (%{GREEDYDATA:extra})?.
Pas de validation. Dissect ne vérifié pas que %{status} est bien un nombre. Il prend tout ce qui est entre les delimiteurs. Un champ corrompu passe sans erreur.
Pas de formats multiples. Grok accepte un tableau de patterns avec break_on_match. Dissect a un seul mapping par filtre.
L'arbre de décision
Le format de tes logs est-il fixe ?
├── Oui (espaces, pipes, crochets, separateurs reguliers)
│ └── Dissect
│ └── + Grok apres si besoin de parser un sous-champ
│
└── Non (formats variables, champs optionnels, regex necessaires)
└── Grok
En pratique, le pattern le plus courant est de combiner les deux :
filter {
# Dissect pour le gros du travail (rapide)
dissect {
mapping => {
"message" => "%{timestamp} %{level} [%{service}] %{body}"
}
}
# Grok pour parser un sous-champ complexe (si necessaire)
if [body] =~ /^(GET|POST|PUT|DELETE)/ {
grok {
match => { "body" => "%{WORD:method} %{URIPATH:url} %{INT:status:int} %{INT:duration:int}ms" }
remove_field => ["body"]
}
}
}
Dissect decoupe la structure fixe (timestamp, level, service), et Grok ne parse que le body qui a un format variable. Le moteur regex ne s'exécuté que sur une petite partie de la ligne.
Sur paltemps.fr, c'est la stratégie qu'on utilise sur les pipelines a haut debit : Dissect pour la structure, Grok pour les détails.
Gestion des échecs
Comme Grok, Dissect ajoute un tag en cas d'échec :
filter {
dissect {
mapping => {
"message" => "%{ts} %{level} %{msg}"
}
tag_on_failure => ["_dissectfailure"]
}
}
Le tag par défaut est _dissectfailure. Surveille-le comme tu surveillerais _grokparsefailure.
Résumé
- Dissect parse sans regex, en se basant sur des delimiteurs fixes
- 5 a 10 fois plus rapide que Grok sur les memes donnees
- Operateurs :
+(append),->(skip padding),?/&(key-value) convert_datatypeévité un mutate séparée- Ne gere pas les champs optionnels ni les formats multiples
- Combine Dissect (structure) + Grok (détails) pour le meilleur des deux mondes
Precedent : 09 - Le filtre Grok | Suivant : 11 - Le filtre Mutate