Logstash pour les devs - 19 - Gerer le multiline : stack traces et logs multi-lignes

Configurer le multiline dans Logstash et Filebeat pour des stack traces Java, des logs Python et des requêtes SQL.

  1. 01 Logstash pour les devs - 00 - Pourquoi Logstash existe encore en 2026
  2. 02 Logstash pour les devs - 01 - L'Elastic Stack de A a Z
  3. 03 Logstash pour les devs - 02 - Installer Logstash avec Docker en 5 minutes
  4. 04 Logstash pour les devs - 03 - Anatomie d'un pipeline Logstash
  5. 05 Logstash pour les devs - 04 - Inputs stdin et file : lire des donnees locales
  6. 06 Logstash pour les devs - 05 - Input Beats : recevoir des logs de Filebeat
  7. 07 Logstash pour les devs - 06 - Inputs HTTP, TCP et UDP : recevoir des donnees réseau
  8. 08 Logstash pour les devs - 07 - Inputs Kafka et JDBC : sources avancees
  9. 09 Logstash pour les devs - 08 - Les codecs : decoder et encoder les donnees
  10. 10 Logstash pour les devs - 09 - Le filtre Grok : parser n'importe quel log
  11. 11 Logstash pour les devs - 10 - Le filtre Dissect : parser sans regex
  12. 12 Logstash pour les devs - 11 - Le filtre Mutate : transformer les champs
  13. 13 Logstash pour les devs - 12 - Filtres Date et GeoIP : temps et geolocalisation
  14. 14 Logstash pour les devs - 13 - Filtres KV, JSON et XML : parser les formats structures
  15. 15 Logstash pour les devs - 14 - Le filtre Ruby : quand les autres ne suffisent pas
  16. 16 Logstash pour les devs - 15 - Filtres Aggregate et Metrics : correler les événements
  17. 17 Logstash pour les devs - 16 - Conditionnels et contrôle de flux
  18. 18 Logstash pour les devs - 17 - Output Elasticsearch : envoyer les donnees
  19. 19 Logstash pour les devs - 18 - Outputs file, stdout et les autres
  20. 20 Logstash pour les devs - 19 - Gerer le multiline : stack traces et logs multi-lignes
  21. 21 Logstash pour les devs - 20 - Pipelines multiples et pipeline-to-pipeline
  22. 22 Logstash pour les devs - 21 - Performance et tuning Logstash
  23. 23 Logstash pour les devs - 22 - Monitoring Logstash : metriques et alertes
  24. 24 Logstash pour les devs - 23 - Dead Letter Queue : ne plus perdre d'événements
  25. 25 Logstash pour les devs - 24 - Sécurité Logstash : SSL, auth et secrets
  26. 26 Logstash pour les devs - 25 - Debugger un pipeline Logstash
  27. 27 Logstash pour les devs - 26 - Tester ses pipelines avant la prod
  28. 28 Logstash pour les devs - 27 - Cas pratique : centraliser des logs applicatifs
  29. 29 Logstash pour les devs - 28 - Cas pratique : ETL avec Logstash et PostgreSQL
  30. 30 Logstash pour les devs - 29 - Cas pratique : enrichir des donnees en temps réel
  31. 31 Logstash pour les devs - 30 - Logstash en production : architecture et bonnes pratiques
  32. 32 Logstash pour les devs - 31 - Glossaire Logstash de A a Z

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


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_lines et timeout protegent 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

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.