19 - Gerer le multiline : stack traces et logs multi-lignes
Ce que tu vas apprendre
- Pourquoi le multiline est un problème dans le traitement de logs
- Configurer le multiline dans Filebeat (approche recommandee)
- Configurer le multiline dans Logstash (codec et filtre)
- Des patterns prets a l'emploi pour Java, Python, Node.js et SQL
- Les pièges du multiline avec les workers
Prerequisites
- Connaitre les codecs (voir article 08)
- Connaitre Filebeat (voir article 05)
Le problème
Par défaut, Logstash créé un événement par ligne. Une stack trace Java de 15 lignes produit 15 événements. La première ligne contient le message d'erreur, les 14 suivantes contiennent des at com.example... sans contexte. Dans Kibana, c'est illisible.
# Sans multiline : 4 evenements
Event 1: "2026-03-31 ERROR Database error"
Event 2: "java.sql.SQLException: Connection refused"
Event 3: " at com.example.db.Pool.getConnection(Pool.java:42)"
Event 4: " at com.example.service.UserService.findById(UserService.java:18)"
# Avec multiline : 1 evenement
Event 1: "2026-03-31 ERROR Database error\njava.sql.SQLException: Connection refused\n at com.example.db.Pool.getConnection(Pool.java:42)\n at com.example.service.UserService.findById(UserService.java:18)"
Le multiline regroupe les lignes de continuation dans le meme événement.
Ou gerer le multiline : Filebeat vs Logstash
C'est la première question a se poser.
| Filebeat | Logstash codec | Logstash filtre | |
|---|---|---|---|
| Ou | Sur la machine source | Dans l'input | Dans le filter |
| Performance | Pas d'impact sur Logstash | Avant la queue | Apres la queue |
| Workers | Pas concerne | OK (avant les workers) | Problème si workers > 1 |
| Recommande | Oui | Cas sans Filebeat | Dernier recours |
La réponse courte : gere le multiline dans Filebeat. C'est là où les lignes sont encore dans l'ordre, avant toute parallelisation.
Si tu n'utilises pas Filebeat (input file, TCP, stdin), utilise le codec multiline sur l'input. Le filtre multiline est le dernier recours, et il force pipeline.workers: 1.
Multiline dans Filebeat
Stack trace Java
Le 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)
2026-03-31 14:23:02 INFO [main] com.example.App - Retrying connection
La config Filebeat :
yamlfilebeat.inputs:
- type: log
paths:
- /var/log/app/*.log
multiline:
type: pattern
pattern: '^\d{4}-\d{2}-\d{2}'
negate: true
match: after
La logique : "une nouvelle ligne de log commence par une date (2026-03-31). Si une ligne ne commence PAS par une date (negate: true), elle est rattachee a la ligne précédente (match: after)."
Résultat : les lignes java.sql.SQLException, at ..., et Caused by: sont toutes rattachees au ERROR [main] qui les precede.
Stack trace Python
Traceback (most recent call last):
File "/app/main.py", line 42, in handle_request
result = db.execute(query)
File "/app/db.py", line 18, in execute
return self.conn.execute(query)
psycopg2.OperationalError: connection refused
yamlmultiline:
type: pattern
pattern: '^Traceback|^\s|^[A-Za-z_][A-Za-z0-9_.]*Error|^[A-Za-z_][A-Za-z0-9_.]*Exception'
negate: false
match: after
Python est plus complique parce que le Traceback commence par Traceback et se termine par le nom de l'exception. Ce pattern rattrape les lignes indentees, les lignes Traceback, et les lignes d'exception.
Logs Node.js (multi-line JSON)
Si ton app Node.js écrit des stack traces dans la console :
Error: ECONNREFUSED 127.0.0.1:5432
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1141:16)
at Protocol._enqueue (/app/node_modules/pg/lib/protocol.js:350:12)
yamlmultiline:
type: pattern
pattern: '^\s+at\s'
negate: false
match: after
Les lignes qui commencent par des espaces suivis de at sont des continuations.
Requetes SQL multi-lignes
2026-03-31 14:23:01 SLOW QUERY (2340ms):
SELECT u.name, u.email, o.total
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE o.created_at > '2026-01-01'
ORDER BY o.total DESC
LIMIT 100;
2026-03-31 14:23:04 INFO Query completed
yamlmultiline:
type: pattern
pattern: '^\d{4}-\d{2}-\d{2}'
negate: true
match: after
Le meme pattern que Java : tout ce qui ne commence pas par un timestamp est une continuation.
Paramètres importants de Filebeat multiline
yamlmultiline:
type: pattern
pattern: '^\d{4}-\d{2}-\d{2}'
negate: true
match: after
max_lines: 500
timeout: 5s
| Paramètre | Défaut | Description |
|---|---|---|
max_lines |
500 | Nombre max de lignes dans un événement multiline |
timeout |
5s | Temps d'attente avant d'emettre un événement incomplet |
max_lines protégé contre les fichiers corrompus qui produiraient un événement de 100 000 lignes. timeout garantit que le dernier événement du fichier est emis meme si aucune nouvelle ligne ne suit.
Multiline dans Logstash (codec)
Si tu n'utilises pas Filebeat (input file direct, par exemple) :
input {
file {
path => "/data/java-app.log"
start_position => "beginning"
sincedb_path => "/dev/null"
codec => multiline {
pattern => "^\d{4}-\d{2}-\d{2}"
negate => true
what => "previous"
max_lines => 500
max_bytes => "10 MiB"
}
}
}
La syntaxe est différente de Filebeat :
| Filebeat | Logstash codec |
|---|---|
match: after |
what => "previous" |
match: before |
what => "next" |
what => "previous" : la ligne de continuation est rattachee a l'événement précédent. C'est équivalent a match: after dans Filebeat.
Multiline dans Logstash (filtre)
Le filtre multiline existe aussi, mais il est déprécié. Utilise-le uniquement si tu n'as pas le choix :
filter {
multiline {
pattern => "^\d{4}-\d{2}-\d{2}"
negate => true
what => "previous"
}
}
Le problème : le filtre multiline a besoin de voir les événements dans l'ordre. Avec pipeline.workers > 1, les événements peuvent arriver desordonnes. Résultat : des stack traces melangees entre différentes erreurs.
Solution : force pipeline.workers: 1 dans pipelines.yml. Mais ca tue les performances.
Le piège des workers
C'est le problème le plus courant avec le multiline. Le voici en schema :
Sans multiline, workers = 4 :
Ligne 1 ──> Worker 1 ──> Event 1
Ligne 2 ──> Worker 2 ──> Event 2 (devrait etre dans Event 1)
Ligne 3 ──> Worker 3 ──> Event 3 (devrait etre dans Event 1)
Ligne 4 ──> Worker 4 ──> Event 4
Avec codec multiline sur l'input :
Les lignes sont regroupees AVANT d'entrer dans la queue.
La queue recoit des evenements complets.
Les workers traitent des evenements deja regroupes.
→ Pas de probleme.
Avec filtre multiline :
Les lignes entrent dans la queue une par une.
Les workers les prennent dans le desordre.
Le filtre essaie de regrouper, mais les lignes d'une meme
stack trace sont reparties entre les workers.
→ Stack traces melangees.
C'est pour ca que le multiline doit etre gere le plus tot possible : dans Filebeat (ideal), ou dans le codec de l'input Logstash.
Patterns prets a l'emploi
Java / Kotlin / Scala
yaml# Filebeat
multiline:
type: pattern
pattern: '^\d{4}-\d{2}-\d{2}|^[A-Z][a-z]{2} \d{2},'
negate: true
match: after
# Logstash codec
codec => multiline {
pattern => "^\d{4}-\d{2}-\d{2}|^[A-Z][a-z]{2} \d{2},"
negate => true
what => "previous"
}
Gere les timestamps ISO (2026-03-31) et les timestamps Log4j (Mar 31, 2026).
Python
yamlmultiline:
type: pattern
pattern: '^Traceback|^\s+File|^\s+|^[A-Za-z_].*Error:|^[A-Za-z_].*Exception:'
negate: false
match: after
Go (panic)
yamlmultiline:
type: pattern
pattern: '^\s|^goroutine\s|^runtime\.'
negate: false
match: after
Les panics Go commencent par goroutine N [running]: et les lignes de stack sont indentees.
JSON multi-lignes (pretty-printed)
Si une application écrit du JSON indente sur plusieurs lignes :
yamlmultiline:
type: pattern
pattern: '^\{'
negate: true
match: after
Chaque { en début de ligne marque un nouvel événement. Tout le reste est une continuation.
Mieux : configure ton application pour écrire du JSON sur une seule ligne. C'est la vraie solution.
La meilleure solution : des logs structures
Le multiline est un pansement. Le vrai problème, c'est que les applications ecrivent des logs non structures sur plusieurs lignes.
La solution propre : écrire des logs JSON sur une seule ligne.
json{"timestamp":"2026-03-31T14:23:01Z","level":"ERROR","service":"api","message":"Database error","stacktrace":"java.sql.SQLException: Connection refused\n at com.example.db.Pool.getConnection(Pool.java:42)\n at com.example.service.UserService.findById(UserService.java:18)"}
La stack trace entière est dans le champ stacktrace, sur une seule ligne JSON. Pas besoin de multiline. Logstash parse le JSON avec le codec json, et la stack trace est un champ text dans Elasticsearch.
Sur paltemps.fr, toutes les applications ecrivent en JSON structure. Le multiline ne sert que pour les logs tiers qu'on ne contrôle pas (Nginx, PostgreSQL, services tiers).
Résumé
- Le multiline regroupe plusieurs lignes en un seul événement (stack traces, SQL, JSON pretty)
- Gere le multiline dans Filebeat (recommande) ou dans le codec de l'input Logstash
- Le filtre multiline est déprécié et incompatible avec
workers > 1 negate: true+match: after= "une ligne qui ne matche pas le pattern est une continuation"max_linesettimeoutprotegent contre les événements geants et les fichiers incomplets- La meilleure solution est d'écrire des logs JSON sur une seule ligne
Precedent : 18 - Outputs file, stdout et autres | Suivant : 20 - Pipelines multiples