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).
| Couche | Rôle |
|---|---|
| SLF4J | API de logging (coques statiques, reliées à Logback au runtime) |
| Logback | Moteur : appenders, layouts, filtres, configurations |
| Jackson | Sé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: WARNRè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.logPiège : définir
logging.level.root: DEBUGen production sans package spécifique inonde le système de logs. Toujours isoler leDEBUGsur 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 nonlogback.xml) permet d’utiliser les profils Spring (<springProfile>) 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 unfinallycorrompt 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 nonlogback.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)