08 - Les codecs : decoder et encoder les donnees
Ce que tu vas apprendre
- Ce qu'est un codec et sa différence avec un filtre
- Les codecs d'entree : plain, json, json_lines, csv, multiline
- Les codecs de sortie : rubydebug, json, json_lines, dots
- Quand utiliser un codec plutot qu'un filtre
- Gerer les stack traces Java avec le codec multiline
Prerequisites
- Avoir compris les inputs et outputs (voir articles 04 a 07)
Codec vs filtre : quelle différence
Pendant mes premières semaines avec Logstash, je confondais les deux. J'avais un input avec des donnees JSON, et je ne savais pas si je devais mettre codec => json sur l'input ou ajouter un json {} dans le bloc filter. Les deux marchent. Mais ils n'interviennent pas au meme moment.
Un codec agit a l'entree ou a la sortie du pipeline. Il transforme les octets bruts en événements (decodage) ou les événements en octets (encodage). Il est attache a un input ou un output.
Un filtre agit au milieu du pipeline, apres que l'événement a ete créé par l'input. Il modifie les champs d'un événement existant.
Octets bruts Octets formattes
│ ^
▼ │
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ CODEC │───>│ FILTER │───>│ FILTER │───>│ CODEC │
│ (decode) │ │ (grok) │ │ (mutate) │ │ (encode) │
│ │ │ │ │ │ │ │
│ input │ │ pipeline │ │ pipeline │ │ output │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
Regle simple : si tes donnees d'entree sont dans un format standard (JSON, CSV), utilise un codec. Si tu dois faire du parsing custom (Grok sur du texte libre), utilise un filtre.
Les codecs d'entree
plain (défaut)
Le codec par défaut. Chaque chunk de donnees reçu par l'input devient le champ message d'un événement. Pas de parsing, pas de transformation.
input {
stdin { codec => plain }
}
Entree : 2026-03-31 ERROR timeout
Résultat : { "message": "2026-03-31 ERROR timeout" }
C'est le bon choix quand tu vas parser avec Grok ou Dissect dans le filtre.
json
Parse un objet JSON complet. Chaque champ JSON devient un champ de l'événement.
input {
http { codec => json }
}
Entree : {"level":"ERROR","service":"api","duration":500}
Résultat :
json{
"level": "ERROR",
"service": "api",
"duration": 500
}
Si le JSON est invalide, l'événement a un tag _jsonparsefailure et les donnees brutes restent dans message.
json_lines
Comme json, mais attend un objet JSON par ligne (format NDJSON). Chaque ligne delimitee par \n est un événement.
input {
tcp {
port => 5000
codec => json_lines
}
}
Entree (sur une connexion TCP persistante) :
{"level":"INFO","msg":"event 1"}
{"level":"ERROR","msg":"event 2"}
{"level":"WARN","msg":"event 3"}
Résultat : 3 événements separes.
La différence entre json et json_lines :
| json | json_lines | |
|---|---|---|
| Entree attendue | Un objet JSON par requête/message | Un objet JSON par ligne |
| Separateur | Fin du message | \n |
| Use case | HTTP POST, message Kafka | TCP, fichiers NDJSON |
| Gestion du flux | Un événement par appel | Plusieurs événements par connexion |
csv
Parse des lignes CSV avec un separateur configurable.
input {
file {
path => "/data/products.csv"
start_position => "beginning"
sincedb_path => "/dev/null"
codec => csv {
separator => ","
columns => ["id", "name", "price", "category", "in_stock"]
}
}
}
Entree : 1,MacBook Pro,2799.00,laptops,true
Résultat :
json{
"id": "1",
"name": "MacBook Pro",
"price": "2799.00",
"category": "laptops",
"in_stock": "true"
}
Attention : tout est en string. Tu dois convertir les types dans un filtre mutate :
filter {
mutate {
convert => {
"price" => "float"
"id" => "integer"
}
}
}
Le codec CSV est un raccourci. Tu peux aussi utiliser le filtre CSV si tu veux plus de contrôle (gestion des guillemets, des sauts de ligne dans les champs, etc.).
multiline
C'est le codec le plus deroutant au début, et un des plus utiles en pratique. Il regroupe plusieurs lignes en un seul événement.
Le cas classique : les stack traces Java. Une exception Java, c'est une ligne d'erreur suivie de dizaines de lignes at com.example.... Sans multiline, chaque ligne at ... devient un événement séparé. Inutilisable.
input {
file {
path => "/data/java-app.log"
start_position => "beginning"
sincedb_path => "/dev/null"
codec => multiline {
pattern => "^\s|^Caused by:"
what => "previous"
negate => false
}
}
}
Le fichier de log :
2026-03-31 14:23:01 ERROR [main] com.example.App - Database error
java.sql.SQLException: Connection refused
at com.example.db.Pool.getConnection(Pool.java:42)
at com.example.service.UserService.findById(UserService.java:18)
at com.example.api.UserController.getUser(UserController.java:31)
Caused by: java.net.ConnectException: Connection refused
at java.base/sun.nio.ch.Net.connect0(Native Method)
at java.base/sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:796)
2026-03-31 14:23:02 INFO [main] com.example.App - Retrying connection
Sans multiline : 9 événements (un par ligne). Avec multiline : 2 événements. Le premier contient la stack trace complète, le deuxieme est le message INFO.
La logique du multiline :
pattern: une regex. Ici, les lignes qui commencent par un espace (^\s) ou parCaused by:.what => "previous": si la ligne matche le pattern, elle est rattachee a l'événement précédent.negate => false: le pattern decrit les lignes de continuation (pas les debuts d'événements).
Un moyen de comprendre : "si la ligne commence par un espace ou Caused by, c'est la suite de l'événement précédent".
Pour inverser la logique (le pattern decrit le début d'un nouvel événement) :
codec => multiline {
pattern => "^%{TIMESTAMP_ISO8601}"
what => "previous"
negate => true
}
"Si la ligne commence par un timestamp, c'est un nouvel événement. Sinon, c'est la suite du précédent." Les deux approches sont equivalentes. Utilise celle que tu trouves la plus lisible.
On reviendra sur le multiline en détail dans l'article 19, avec des patterns pour Python, Node.js et les requêtes SQL multi-lignes.
Les codecs de sortie
rubydebug
Le codec de debug par excellence. Affiche l'événement dans un format lisible, avec l'indentation et les types.
output {
stdout { codec => rubydebug }
}
{
"@timestamp" => 2026-03-31T14:23:01.000Z,
"level" => "ERROR",
"service" => "api-users",
"duration_ms" => 5023,
"message" => "Connection timeout"
}
C'est le codec qu'on utilise depuis le début de cette serie pour voir les événements dans la console. Ne l'utilise jamais en production : il est lent et verbeux.
Pour voir les metadonnees (@metadata), ajoute l'option :
output {
stdout { codec => rubydebug { metadata => true } }
}
json
Encode chaque événement en JSON sur une seule ligne. C'est le format standard pour les outputs fichier et les integrations.
output {
file {
path => "/data/output.json"
codec => json
}
}
Résultat dans le fichier :
json{"@timestamp":"2026-03-31T14:23:01.000Z","level":"ERROR","service":"api-users","duration_ms":5023}
json_lines
Comme json, avec un saut de ligne apres chaque objet JSON. C'est le format NDJSON.
output {
file {
path => "/data/output.ndjson"
codec => json_lines
}
}
La différence est subtile : json n'ajoute pas de \n entre les objets, json_lines si. Pour un fichier, json_lines est presque toujours le bon choix (un objet par ligne, facile a grep).
line
Encode un événement en une seule ligne avec un format custom.
output {
file {
path => "/data/output.log"
codec => line {
format => "%{@timestamp} %{level} [%{service}] %{message}"
}
}
}
Résultat : 2026-03-31T14:23:01.000Z ERROR [api-users] Connection timeout
Pratique pour générer des fichiers de log dans un format spécifique.
dots
Affiche un point par événement traite. Rien d'autre.
output {
stdout { codec => dots }
}
Résultat : ...........
Ca sert a vérifier le debit du pipeline sans polluer la console. Si tu vois les points defiler vite, ca marche. C'est un compteur visuel.
Tableau recapitulatif
| Codec | Direction | Format | Use case |
|---|---|---|---|
plain |
Entree | Texte brut | Logs non structures, parser avec Grok |
json |
Entree/Sortie | Objet JSON | Logs JSON, API, Kafka |
json_lines |
Entree/Sortie | NDJSON | TCP, fichiers multi-événements |
csv |
Entree | CSV | Import de fichiers CSV |
multiline |
Entree | Texte multi-ligne | Stack traces, SQL multi-lignes |
rubydebug |
Sortie | Debug lisible | Dev, debug |
line |
Sortie | Format custom | Fichiers de log custom |
dots |
Sortie | Points | Mesurer le debit |
Codec sur l'input vs filtre : l'arbre de décision
Les donnees d'entree sont en JSON ?
├── Oui → codec => json (ou json_lines si TCP/fichier)
└── Non
├── CSV ? → codec => csv
├── Multi-lignes (stack traces) ? → codec => multiline
└── Texte libre ? → codec => plain + filtre Grok/Dissect
Quand les deux sont possibles (JSON en entree : codec json ou filtre json sur message), préféré le codec. Il est exécuté avant que l'événement entre dans la queue, donc il consomme moins de ressources pipeline.
Sur paltemps.fr, la regle est simple : les applications ecrivent en JSON (codec json sur l'input), les logs système et Nginx sont en texte libre (codec plain + filtre Grok).
Résumé
- Un codec agit a l'entree ou a la sortie du pipeline, un filtre agit au milieu
plainest le défaut : une ligne de texte = un événementjsonparse un objet JSON par message,json_linesparse un objet JSON par lignemultilineregroupe plusieurs lignes en un seul événement (stack traces, SQL)rubydebugest le codec de debug pour stdout,json_linesest le standard pour les fichiers- Quand c'est possible, préféré un codec a un filtre (plus performant)
Precedent : 07 - Inputs Kafka et JDBC | Suivant : 09 - Le filtre Grok