21/01/2025

Filtrer des fichiers logs qui contiennent du JSON et du texte avec jq

Comment extraire un champ particulier des lignes de log en JSON venant d'un fichier qui contient aussi des lignes qui ne sont pas du JSON ?

C'est la question que posait quelqu'un sur le Fediverse hier. Enfin pas tout de suite, sa question initiale était "quelqu'un a un guide de jq pour les idiots ?" et ça m'a titillé parce que jq est un outil que j'utilise pas mal dans mon travail.

C'est un cas d'usage très "vraie vie" avec des pièges qui sont aussi très "vraie vie". Donc faisons l'exercice ensemble.

D'abord, des données dans un fichier input.log :

Ceci n'est pas du JSON
{"log-level": "info", "log-type": "foo", "message": "Ceci par contre c'est du JSON"}
{"log-level": "warning", "log-type": "bar", "message": "Ceci aussi est du JSON"}
Oh non, du pas-JSON !
Quelle horreur
{"log-level": "warning", "log-type": "foo", "message": "Pfiou, quel soulagement"}
{"log-level": "error", "log-type": "bar", "message": "C'est tellement mieux"}
Horreur !

Tentons de donner ça à manger à jq :

$ cat input.log | jq
parse error: Invalid numeric literal at line 1, column 5

Sans surprise il s'étrangle sur la première ligne, puisqu'il s'attend à du JSON.

Dans le manuel on mentionne l'option --raw-input qui désactive purement le parsing JSON et transmet chaque ligne au filtre en tant que chaîne.

$ cat input.log | jq --raw-input
"Ceci n'est pas du JSON"
"{\"log-level\": \"info\", \"log-type\": \"foo\", \"message\": \"Ceci par contre c'est du JSON\"}"
"{\"log-level\": \"warning\", \"log-type\": \"bar\", \"message\": \"Ceci aussi est du JSON\"}"
"Oh non, du pas-JSON !"
"Quelle horreur"
"{\"log-level\": \"warning\", \"log-type\": \"foo\", \"message\": \"Pfiou, quel soulagement\"}"
"{\"log-level\": \"error\", \"log-type\": \"bar\", \"message\": \"C'est tellement mieux\"}"
"Horreur !"

Du coup on peut confier le parsing au filtre lui-même en utilisant le builtin fromjson :

$ cat input.log | jq --raw-input 'fromjson'
jq: error (at <stdin>:1): Invalid numeric literal at line 1, column 5 (while parsing 'Ceci n'est pas du JSON')
{
    "log-level": "info",
    "log-type": "foo",
    "message": "Ceci par contre c'est du JSON"
}
{
    "log-level": "warning",
    "log-type": "bar",
    "message": "Ceci aussi est du JSON"
}
jq: error (at <stdin>:4): Invalid numeric literal at line 1, column 3 (while parsing 'Oh non, du pas-JSON !')
jq: error (at <stdin>:5): Invalid numeric literal at line 1, column 7 (while parsing 'Quelle horreur')
{
    "log-level": "warning",
    "log-type": "foo",
    "message": "Pfiou, quel soulagement"
}
{
    "log-level": "error",
    "log-type": "bar",
    "message": "C'est tellement mieux"
}
jq: error (at <stdin>:8): Invalid numeric literal at line 1, column 8 (while parsing 'Horreur !')

Ajoutons-y l'opérateur ? pour la suppression d'erreur (qui est un raccourci pour un try ... catch avec clause catch vide :

$ cat input.log | jq --raw-input 'fromjson?'
{
    "log-level": "info",
    "log-type": "foo",
    "message": "Ceci par contre c'est du JSON"
}
{
    "log-level": "warning",
    "log-type": "bar",
    "message": "Ceci aussi est du JSON"
}
{
    "log-level": "warning",
    "log-type": "foo",
    "message": "Pfiou, quel soulagement"
}
{
    "log-level": "error",
    "log-type": "bar",
    "message": "C'est tellement mieux"
}

Bien ! Maintenant il reste à filtrer et extraire le champ qu'on veut. Disons qu'on veut ne garder que les entrées qui ont leur log-level à warning :

$ cat input.log | jq --raw-input 'fromjson? | select (."log-level" == "warning")'
{
    "log-level": "warning",
    "log-type": "bar",
    "message": "Ceci aussi est du JSON"
}
{
    "log-level": "warning",
    "log-type": "foo",
    "message": "Pfiou, quel soulagement"
}

Les guillemets autour de log-level dans le filtre sont nécessaires à cause du trait d'union. Sans ça ce serait interprété comme une soustraction.

Reste maintenant à ne garder que le champ message :

$ cat input.log | jq --raw-input 'fromjson? | select (."log-level" == "warning") | .message'
"Ceci aussi est du JSON"
"Pfiou, quel soulagement"

Et ça fait le boulot.


Tags:

cli | jq | json


Impressions : Fleshbound (22/01/2025)20 janvier 2025 (20/01/2025)