13 - Filtres KV, JSON et XML : parser les formats structures
Ce que tu vas apprendre
- Parser des paires clé=valeur avec le filtre KV
- Extraire du JSON inline (un champ qui contient du JSON dans un string)
- Parser du XML et naviguer avec XPath
- Quand utiliser un filtre vs un codec
Prerequisites
- Connaitre les bases des filtres Logstash (voir articles 09 a 11)
Le filtre KV : paires clé-valeur
Les logs applicatifs utilisent souvent un format clé=valeur. Pas du JSON, pas du CSV, juste des paires separees par des espaces :
ts=2026-03-31T14:23:01Z level=ERROR service=api-users method=POST path=/users status=500 duration=5023 msg="Connection refused"
Le filtre KV parse ca sans regex.
Configuration de base
filter {
kv {
source => "message"
field_split => " "
value_split => "="
}
}
Résultat :
json{
"ts": "2026-03-31T14:23:01Z",
"level": "ERROR",
"service": "api-users",
"method": "POST",
"path": "/users",
"status": "500",
"duration": "5023",
"msg": "Connection refused"
}
KV a détecté le separateur = entre les clés et les valeurs, et les espaces entre les paires. Les valeurs entre guillemets ("Connection refused") sont extraites sans les guillemets.
Paramètres utiles
filter {
kv {
source => "message"
field_split => " "
value_split => "="
# Ne garder que certaines cles
include_keys => ["level", "service", "status", "duration", "msg"]
# Ou exclure des cles
# exclude_keys => ["ts"]
# Prefixer les champs extraits
prefix => "app_"
# Stocker dans un sous-objet
target => "parsed"
# Supprimer les guillemets des valeurs
trim_value => "\""
trim_key => " "
}
}
Avec target => "parsed", les champs vont dans [parsed][level], [parsed][service], etc. Ca évité de polluer la racine de l'événement.
Parser des query strings
KV est parfait pour les query strings d'URL :
# Ligne de log avec une URL
GET /search?q=logstash&page=2&lang=fr HTTP/1.1
Apres extraction de la query string par Grok :
filter {
grok {
match => { "message" => "%{WORD:method} %{URIPATH:path}\?%{DATA:query_string} HTTP/%{NUMBER}" }
}
kv {
source => "query_string"
field_split => "&"
value_split => "="
target => "params"
}
}
Résultat : params.q = "logstash", params.page = "2", params.lang = "fr".
Le filtre JSON
Codec json vs filtre json
On a vu le codec json dans l'article 08. Le codec agit a l'entree, il parse les donnees brutes. Le filtre JSON agit au milieu du pipeline, il parse un champ qui contient du JSON dans un string.
Le cas classique : un log texte qui contient un champ JSON embarque.
2026-03-31 14:23:01 INFO [api] Request completed {"user_id":42,"action":"login","ip":"192.168.1.10","duration_ms":156}
Grok extrait la partie JSON dans un champ, puis le filtre JSON la parse.
Configuration de base
filter {
grok {
match => {
"message" => "%{TIMESTAMP_ISO8601:ts} %{LOGLEVEL:level} \[%{DATA:service}\] %{DATA:log_msg} %{GREEDYDATA:json_data}"
}
}
json {
source => "json_data"
target => "request"
remove_field => ["json_data"]
}
}
Résultat :
json{
"ts": "2026-03-31 14:23:01",
"level": "INFO",
"service": "api",
"log_msg": "Request completed",
"request": {
"user_id": 42,
"action": "login",
"ip": "192.168.1.10",
"duration_ms": 156
}
}
Le target => "request" met les champs JSON dans un sous-objet. Sans target, les champs se retrouvent a la racine de l'événement et peuvent ecraser des champs existants.
JSON a la racine
Si tout l'événement est du JSON (et que tu n'as pas utilise le codec json sur l'input pour une raison quelconque) :
filter {
json {
source => "message"
}
}
Sans target, les champs JSON ecrasent les champs existants. Si le JSON contient un champ message, il ecrase le message original. C'est souvent ce qu'on veut pour des logs JSON purs.
Gestion des erreurs
Si le champ ne contient pas du JSON valide :
filter {
json {
source => "json_data"
tag_on_failure => ["_jsonparsefailure"]
}
}
L'événement est garde tel quel avec le tag _jsonparsefailure. Le champ source n'est pas modifie.
Le filtre XML
Plus rare, mais indispensable quand tu recois du XML (APIs legacy, fichiers de config, logs d'applications Java enterprise).
Configuration de base
filter {
xml {
source => "xml_data"
target => "parsed_xml"
store_xml => true
}
}
Entree (champ xml_data) :
xml<event>
<timestamp>2026-03-31T14:23:01Z</timestamp>
<level>ERROR</level>
<service>payment-gateway</service>
<message>Transaction failed</message>
<transaction>
<id>txn-abc123</id>
<amount>49.99</amount>
<currency>EUR</currency>
</transaction>
</event>
Résultat :
json{
"parsed_xml": {
"timestamp": ["2026-03-31T14:23:01Z"],
"level": ["ERROR"],
"service": ["payment-gateway"],
"message": ["Transaction failed"],
"transaction": [{
"id": ["txn-abc123"],
"amount": ["49.99"],
"currency": ["EUR"]
}]
}
}
Note : XML est converti en tableaux (meme pour les éléments uniques), parce que XML permet des éléments repetes.
Extraire avec XPath
Pour cibler des éléments spécifiques sans garder tout le XML :
filter {
xml {
source => "xml_data"
store_xml => false
xpath => {
"/event/level/text()" => "level"
"/event/service/text()" => "service"
"/event/message/text()" => "error_msg"
"/event/transaction/id/text()" => "transaction_id"
"/event/transaction/amount/text()" => "amount"
}
}
}
Résultat :
json{
"level": ["ERROR"],
"service": ["payment-gateway"],
"error_msg": ["Transaction failed"],
"transaction_id": ["txn-abc123"],
"amount": ["49.99"]
}
Les résultats XPath sont toujours des tableaux. Pour obtenir un string simple, ajoute un mutate :
filter {
# ... xml filter ...
mutate {
join => {
"level" => ""
"service" => ""
"error_msg" => ""
"transaction_id" => ""
}
convert => { "amount" => "float" }
}
}
XML avec namespaces
Si le XML utilise des namespaces :
xml<ns:event xmlns:ns="http://example.com/logs">
<ns:level>ERROR</ns:level>
</ns:event>
filter {
xml {
source => "xml_data"
store_xml => false
namespaces => { "ns" => "http://example.com/logs" }
xpath => {
"/ns:event/ns:level/text()" => "level"
}
}
}
Tableau recapitulatif : quel filtre pour quel format
| Format d'entree | Filtre recommande | Exemple |
|---|---|---|
key=value key2=value2 |
KV | Logs structurels, query strings |
{"key":"value"} (tout l'événement) |
Codec json | API, apps modernes |
text... {"json":true} (JSON embarque) |
Filtre JSON | Logs mixtes texte+JSON |
<xml>...</xml> |
Filtre XML | APIs legacy, SOAP, configs |
col1,col2,col3 |
Codec CSV ou filtre CSV | Fichiers CSV |
texte libre |
Grok ou Dissect | Logs non structures |
Ma recommandation sur paltemps.fr : si tu contrôles le format de tes logs, ecris en JSON. Le codec JSON sur l'input est le parsing le plus rapide et le plus fiable. Pas de regex, pas d'ambiguite. Si tu ne contrôles pas le format, utilise le filtre adapte au format que tu recois.
Résumé
- Le filtre KV parse les paires
cle=valeursans regex, avec des separateurs configurables - KV est parfait pour les logs structures et les query strings d'URL
- Le filtre JSON parse du JSON contenu dans un champ string (différent du codec JSON)
targetévité les collisions de champs quand tu parses dans un sous-objet- Le filtre XML parse du XML avec
store_xmlouxpathpour cibler des éléments - XPath renvoie des tableaux : utilise
mutate { join }pour obtenir des strings
Precedent : 12 - Filtres Date et GeoIP | Suivant : 14 - Le filtre Ruby