Skip to Content
BackendLogging

Logging dans Spring Boot / microservices JVM

Référence opérationnelle pour configurer, structurer et déboguer les logs d’une application JVM — SLF4J, Logback, logs JSON, MDC et pièges courants.

Pourquoi cette page : Spring Boot fourni une configuration Logback de base dans application.yml (voir spring-boot), mais un projet en production a besoin de bien plus : logs JSON, corrélation de requêtes, niveaux granulaires par service. Cette page rassemble le minimum opérationnel.

Architecture de base

Spring Boot utilise SLF4J comme façade et Logback comme implémentation par défaut (via spring-boot-starter-logging).

CoucheRôle
SLF4JAPI de logging (coques statiques, reliées à Logback au runtime)
LogbackMoteur : appenders, layouts, filtres, configurations
JacksonSérialisation JSON (utilisé par logstash-logback-encoder)

Dépendance optionnelle pour les logs JSON :

<dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>7.4</version> </dependency>

Configuration rapide

Niveaux de log

# application.yml logging: level: root: INFO # Désactive les avertissements Spring internes org.springframework: WARN # Mode debug pour le service de paiement uniquement com.monapp.payment: DEBUG # Silencieux sur les drivers JDBC (bruit excessif) org.hibernate.SQL: WARN

Règle : en production, laisser root: INFO. N’abaisser DEBUG que sur un package spécifique pour un débogage ciblé.

Profils de logging

# application-dev.yml logging: level: root: DEBUG com.zaxxer.hikari: DEBUG pattern: console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" file: name: logs/dev.log max-size: 10MB max-history: 7 # application-prod.yml logging: level: root: WARN com.monapp: INFO file: name: /var/log/monapp/app.log

Piège : définir logging.level.root: DEBUG en production sans package spécifique inonde le système de logs. Toujours isoler le DEBUG sur un package.

Logs structurés (JSON)

Les logs JSON sont indispensables pour le parsing par des outils comme ELK, Loki ou Datadog.

Configuration Logback avec logstash-logback-encoder

<!-- src/main/resources/logback-spring.xml --> <configuration> <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder"> <providers> <timestamp> <timeZone>UTC</timeZone> </timestamp> <pattern> <pattern> { "severity": "%level", "service": "${spring.application.name:-}", "trace": "%X{traceId:-}", "span": "%X{spanId:-}", "thread": "%thread", "class": "%logger{40}", "message": "%message" } </pattern> </pattern> </providers> </encoder> </appender> <springProfile name="prod"> <root level="INFO"> <appender-ref ref="JSON"/> </root> </springProfile> <springProfile name="dev"> <root level="DEBUG"> <appender-ref ref="JSON"/> </root> </springProfile> </configuration>

Note : placer le fichier sous le nom logback-spring.xml (et non logback.xml) permet d’utiliser les profils Spring (&lt;springProfile&gt;) dans la configuration.

Corrélation de requêtes (MDC)

Le Mapped Diagnostic Context (MDC) associe des clés dynamiques à chaque thread, ce qui permet de reconstituer le chemin d’une requête dans les logs.

Générer un ID de corrélation dans un filtre

package com.monapp.infrastructure.web; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.MDC; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import java.util.UUID; @Component @Order(Ordered.HIGHEST_PRECEDENCE) public class CorrelationFilter extends OncePerRequestFilter { private static final String CORRELATION_ID = "traceId"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String correlationId = request.getHeader(CORRELATION_ID); if (correlationId == null || correlationId.isBlank()) { correlationId = UUID.randomUUID().toString(); } MDC.put(CORRELATION_ID, correlationId); response.setHeader(CORRELATION_ID, correlationId); try { chain.doFilter(request, response); } finally { MDC.remove(CORRELATION_ID); } } }

Afficher l’ID dans le pattern de log

<configuration> <property name="CONSOLE_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId:-}] %logger{36} - %msg%n"/> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${CONSOLE_PATTERN}</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="CONSOLE"/> </root> </configuration>

Piège : oublier MDC.remove() dans un finally corrompt tous les logs du thread. Dans un pool de threads (Tomcat, WebFlux), l’ID de corrélation se propage à la requête suivante et fausse tous les logs qui s’y rattachent.

Résultat dans les logs

2026-07-05 14:32:01.234 [http-nio-8080-exec-3] INFO [traceId=550e8400-e29b-41d4-a716-446655440000] c.m.p.PaymentController - Requête POST /api/paiement reçue 2026-07-05 14:32:01.456 [http-nio-8080-exec-3] DEBUG [traceId=550e8400-e29b-41d4-a716-446655440000] c.m.p.PaymentService - Appel gateway paiement 2026-07-05 14:32:02.001 [http-nio-8080-exec-3] INFO [traceId=550e8400-e29b-41d4-a716-446655440000] c.m.p.PaymentController - Réponse 201 Créé

Fallback chain (Logback)

Logback permet de définir des appenders secondaires qui s’activent quand le primaire échoue (ex. : fichier en cours de rotation).

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/app.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>%d [%thread] %-5level %logger - %msg%n</pattern> </encoder> </appender> <appender name="FALLBACK" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/app-fallback.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>logs/app-fallback.%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>7</maxHistory> </rollingPolicy> <encoder> <pattern>%d [%thread] %-5level %logger - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="FILE"/> <!-- Si FILE échoue, les logs vont dans FALLBACK --> <appender-ref ref="FALLBACK"/> </root>

Pièges courants

Ne pas omettre l’exception dans log.error()

try { // ... } catch (IOException e) { // ❌ Oublier le second paramètre = stack trace perdue log.error("Erreur de lecture : {}", e.getMessage()); // ✅ Bon : inclure l'exception pour avoir le stack trace log.error("Erreur de lecture", e); }

Configuration logback.xml ignorée si logback-spring.xml existe

Logback recherche d’abord logback-test.xml (tests), puis logback-spring.xml (production avec profils), puis logback.xml. Si les deux existent, seul le premier trouvé est chargé. Ne jamais avoir les deux dans le classpath.

Logs sensibles dans les fichiers

Jamais de mots de passe, tokens ou données personnelles dans les logs. Utiliser des masqueurs via Logback ou, mieux, filtrer en amont au niveau du code.

<!-- Masquage simple via filter --> <filter class="ch.qos.logback.core.filter.EvaluatorFilter"> <evaluator> <expression>message.contains("password=")</expression> </evaluator> <onMismatch>NEUTRAL</onMismatch> <onMatch>DENY</onMatch> </filter>

Checklist de production

  • Pattern de log avec traceId / spanId (MDC)
  • Logs en JSON en production (compatible ELK / Loki)
  • Profils séparés : dev (DEBUG + console), prod (INFO + fichier)
  • logback-spring.xml (et non logback.xml) pour les profils Spring
  • Fallback appender pour le cas où le fichier principal est indisponible
  • Taille maximale et rotation des fichiers de log
  • Pas de stack trace complète en production (sauf ERROR)
  • Variables d’environnement pour le niveau de log (débug à la demande)