Spring Boot logging en 2026 : logs structurés pour la production avec Logback et JSON

Guide complet des logs structurés Spring Boot. Configuration Logback JSON, MDC pour le tracing, best practices production et intégration ELK Stack.

Spring Boot structured logging avec Logback et JSON

Les logs textuels traditionnels deviennent rapidement ingérables en production. Avec des centaines d'instances générant des milliers de lignes par seconde, rechercher une erreur spécifique relève du cauchemar. Les logs structurés en JSON transforment cette situation en rendant chaque événement queryable et analysable automatiquement.

Point clé

Spring Boot 3.4+ supporte nativement les logs structurés JSON sans dépendances externes. Pour les versions antérieures, Logback Logstash Encoder reste la référence.

Pourquoi adopter les logs structurés

Limites des logs textuels classiques

Un log textuel typique ressemble à ceci :

text
2026-03-27 10:15:32.456 INFO  [order-service,abc123] c.e.s.OrderService - Order created for user john@example.com, amount: 150.00€, items: 3

Ce format pose plusieurs problèmes en production. L'extraction d'informations spécifiques nécessite des regex complexes et fragiles. La corrélation entre services requiert des conventions strictes que chaque équipe interprète différemment. Les outils d'analyse comme Elasticsearch peinent à indexer efficacement ces chaînes non structurées.

Avantages du format JSON

Le même événement en JSON devient immédiatement exploitable :

json
{
  "@timestamp": "2026-03-27T10:15:32.456Z",
  "level": "INFO",
  "logger": "com.example.service.OrderService",
  "message": "Order created",
  "service": "order-service",
  "traceId": "abc123",
  "userId": "john@example.com",
  "orderId": "ORD-789456",
  "amount": 150.00,
  "currency": "EUR",
  "itemCount": 3
}

Chaque champ devient filtrable et agrégeable. Une requête Elasticsearch peut instantanément trouver toutes les commandes supérieures à 100€ du dernier quart d'heure. Les dashboards Kibana visualisent les tendances sans parsing manuel.

Configuration native Spring Boot 3.4+

Activation des logs JSON structurés

Spring Boot 3.4 introduit le support natif des logs structurés via la propriété logging.structured. Cette approche ne nécessite aucune dépendance supplémentaire.

yaml
# application.yml
# Configuration logs structurés natifs Spring Boot 3.4+
logging:
  structured:
    # Format de sortie : ecs (Elastic), logstash, gelf
    format:
      console: ecs
      file: ecs
  file:
    name: /var/log/app/application.log
  level:
    root: INFO
    com.example: DEBUG

Le format ECS (Elastic Common Schema) garantit la compatibilité directe avec Elasticsearch et Kibana sans configuration supplémentaire.

Personnalisation des champs JSON

Pour ajouter des champs métier à chaque log, Spring Boot permet de configurer des attributs additionnels.

yaml
# application.yml
# Champs personnalisés dans les logs structurés
logging:
  structured:
    format:
      console: ecs
    ecs:
      # Informations service ajoutées à chaque log
      service:
        name: ${spring.application.name}
        version: ${app.version:1.0.0}
        environment: ${spring.profiles.active:default}
        node-name: ${HOSTNAME:unknown}
LoggingConfig.javajava
// Configuration programmatique des champs additionnels
package com.example.logging.config;

import org.springframework.boot.logging.structured.StructuredLogFormatterCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LoggingConfig {

    @Bean
    StructuredLogFormatterCustomizer<EcsStructuredLogFormatter> ecsCustomizer() {
        return formatter -> formatter
            // Ajoute des champs statiques à tous les logs
            .addStaticField("team", "backend")
            .addStaticField("region", System.getenv("AWS_REGION"))
            // Personnalise le formatage des exceptions
            .setIncludeStacktrace(true)
            .setStacktraceMaxLength(5000);
    }
}

Ces champs apparaissent dans chaque ligne de log, facilitant le filtrage par équipe ou région dans les dashboards.

Configuration Logback classique avec JSON Encoder

Dépendance Logstash Encoder

Pour les versions Spring Boot antérieures à 3.4 ou les besoins de personnalisation avancée, Logstash Logback Encoder reste la solution de référence.

xml
<!-- pom.xml -->
<!-- Dépendance pour logs JSON avec Logback -->
<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>7.4</version>
</dependency>

Configuration Logback complète

Le fichier logback-spring.xml offre un contrôle total sur le format de sortie.

xml
<!-- src/main/resources/logback-spring.xml -->
<!-- Configuration Logback pour logs JSON structurés -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- Propriétés Spring Boot -->
    <springProperty scope="context" name="appName" source="spring.application.name" defaultValue="app"/>
    <springProperty scope="context" name="appVersion" source="app.version" defaultValue="1.0.0"/>

    <!-- Appender console JSON pour production -->
    <appender name="JSON_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <!-- Champs personnalisés ajoutés à chaque log -->
            <customFields>{"service":"${appName}","version":"${appVersion}"}</customFields>
            <!-- Inclut le MDC (contexte de tracing) -->
            <includeMdcKeyName>traceId</includeMdcKeyName>
            <includeMdcKeyName>spanId</includeMdcKeyName>
            <includeMdcKeyName>userId</includeMdcKeyName>
            <includeMdcKeyName>requestId</includeMdcKeyName>
            <!-- Format timestamp ISO8601 -->
            <timestampPattern>yyyy-MM-dd'T'HH:mm:ss.SSSZ</timestampPattern>
            <!-- Stack traces complètes -->
            <throwableConverter class="net.logstash.logback.stacktrace.ShortenedThrowableConverter">
                <maxDepthPerThrowable>30</maxDepthPerThrowable>
                <maxLength>4096</maxLength>
                <shortenedClassNameLength>36</shortenedClassNameLength>
                <rootCauseFirst>true</rootCauseFirst>
            </throwableConverter>
        </encoder>
    </appender>

    <!-- Appender fichier rotatif JSON -->
    <appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>/var/log/${appName}/application.json</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>/var/log/${appName}/application.%d{yyyy-MM-dd}.%i.json.gz</fileNamePattern>
            <maxHistory>30</maxHistory>
            <maxFileSize>100MB</maxFileSize>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <customFields>{"service":"${appName}","version":"${appVersion}"}</customFields>
        </encoder>
    </appender>

    <!-- Appender textuel pour développement -->
    <appender name="TEXT_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %highlight(%-5level) [%thread] %cyan(%logger{36}) - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- Activation par profil Spring -->
    <springProfile name="prod,staging">
        <root level="INFO">
            <appender-ref ref="JSON_CONSOLE"/>
            <appender-ref ref="JSON_FILE"/>
        </root>
    </springProfile>

    <springProfile name="dev,local">
        <root level="DEBUG">
            <appender-ref ref="TEXT_CONSOLE"/>
        </root>
    </springProfile>
</configuration>

Cette configuration active les logs JSON uniquement en production tout en conservant des logs lisibles en développement.

Profils Spring

L'utilisation de <springProfile> permet de basculer automatiquement entre formats textuels et JSON selon l'environnement, sans modifier la configuration.

MDC pour le tracing distribué

Propagation du contexte de trace

Le MDC (Mapped Diagnostic Context) permet d'enrichir chaque log avec des informations de contexte comme l'identifiant de requête ou de trace.

TracingFilter.javajava
// Filtre pour injection automatique du contexte de trace
package com.example.logging.filter;

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 TracingFilter extends OncePerRequestFilter {

    // Clés MDC standards pour le tracing
    private static final String TRACE_ID_KEY = "traceId";
    private static final String SPAN_ID_KEY = "spanId";
    private static final String REQUEST_ID_KEY = "requestId";
    private static final String USER_ID_KEY = "userId";

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {

        try {
            // Récupère ou génère les identifiants de trace
            String traceId = extractOrGenerate(request, "X-Trace-Id", TRACE_ID_KEY);
            String spanId = generateSpanId();
            String requestId = extractOrGenerate(request, "X-Request-Id", REQUEST_ID_KEY);
            String userId = request.getHeader("X-User-Id");

            // Injecte dans le MDC pour apparaître dans tous les logs
            MDC.put(TRACE_ID_KEY, traceId);
            MDC.put(SPAN_ID_KEY, spanId);
            MDC.put(REQUEST_ID_KEY, requestId);
            if (userId != null) {
                MDC.put(USER_ID_KEY, userId);
            }

            // Propage aux réponses pour le chaînage inter-services
            response.setHeader("X-Trace-Id", traceId);
            response.setHeader("X-Request-Id", requestId);

            filterChain.doFilter(request, response);

        } finally {
            // Nettoie le MDC après chaque requête
            MDC.clear();
        }
    }

    private String extractOrGenerate(HttpServletRequest request, String header, String key) {
        String value = request.getHeader(header);
        return value != null ? value : UUID.randomUUID().toString().replace("-", "").substring(0, 16);
    }

    private String generateSpanId() {
        return UUID.randomUUID().toString().replace("-", "").substring(0, 8);
    }
}

Chaque log émis pendant le traitement de la requête contiendra automatiquement ces identifiants.

Utilisation du MDC dans le code métier

OrderService.javajava
// Service métier avec logging contextuel enrichi
package com.example.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    private static final Logger log = LoggerFactory.getLogger(OrderService.class);

    public Order createOrder(CreateOrderRequest request) {
        // Ajoute des informations métier au contexte MDC
        MDC.put("orderId", request.getOrderId());
        MDC.put("customerId", request.getCustomerId());

        try {
            log.info("Creating order with {} items", request.getItems().size());

            // Logique métier...
            Order order = processOrder(request);

            log.info("Order created successfully, total: {} {}",
                order.getTotal(), order.getCurrency());

            return order;

        } catch (Exception e) {
            // L'exception apparaît avec tout le contexte MDC
            log.error("Failed to create order", e);
            throw e;
        } finally {
            // Nettoie les clés métier ajoutées
            MDC.remove("orderId");
            MDC.remove("customerId");
        }
    }
}

Le log JSON résultant contient toutes les informations nécessaires pour le debugging :

json
{
  "@timestamp": "2026-03-27T10:15:32.456Z",
  "level": "INFO",
  "logger": "com.example.service.OrderService",
  "message": "Order created successfully, total: 150.00 EUR",
  "traceId": "a1b2c3d4e5f67890",
  "spanId": "12345678",
  "requestId": "req-abc-123",
  "userId": "user-456",
  "orderId": "ORD-789",
  "customerId": "CUST-321"
}

Prêt à réussir tes entretiens Spring Boot ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

Logging asynchrone pour la performance

Configuration du pool de threads

En production, les écritures de logs synchrones impactent la latence des requêtes. L'appender asynchrone découple le logging du thread principal.

xml
<!-- logback-spring.xml -->
<!-- Configuration appender asynchrone haute performance -->
<appender name="ASYNC_JSON" class="ch.qos.logback.classic.AsyncAppender">
    <!-- Taille du buffer de logs en attente -->
    <queueSize>1024</queueSize>
    <!-- Ne jamais bloquer le thread appelant -->
    <neverBlock>true</neverBlock>
    <!-- Seuil avant de dropper les logs DEBUG/TRACE -->
    <discardingThreshold>20</discardingThreshold>
    <!-- Inclut les informations de caller (coûteux) -->
    <includeCallerData>false</includeCallerData>
    <!-- Appender réel pour l'écriture -->
    <appender-ref ref="JSON_FILE"/>
</appender>

<springProfile name="prod">
    <root level="INFO">
        <appender-ref ref="ASYNC_JSON"/>
    </root>
</springProfile>

Métriques du système de logging

Le monitoring du système de logging lui-même évite les pertes silencieuses de logs.

LoggingMetrics.javajava
// Exposition des métriques Logback via Micrometer
package com.example.logging.metrics;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.classic.AsyncAppender;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import jakarta.annotation.PostConstruct;
import java.util.Iterator;

@Component
public class LoggingMetrics {

    private final MeterRegistry registry;

    public LoggingMetrics(MeterRegistry registry) {
        this.registry = registry;
    }

    @PostConstruct
    void registerMetrics() {
        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
        Logger rootLogger = context.getLogger(Logger.ROOT_LOGGER_NAME);

        // Parcourt les appenders pour trouver les AsyncAppender
        Iterator<Appender<ILoggingEvent>> it = rootLogger.iteratorForAppenders();
        while (it.hasNext()) {
            Appender<ILoggingEvent> appender = it.next();
            if (appender instanceof AsyncAppender asyncAppender) {
                registerAsyncMetrics(asyncAppender);
            }
        }
    }

    private void registerAsyncMetrics(AsyncAppender appender) {
        String appenderName = appender.getName();

        // Taille actuelle de la queue
        Gauge.builder("logback.async.queue.size", appender, AsyncAppender::getQueueSize)
            .tag("appender", appenderName)
            .description("Current async appender queue size")
            .register(registry);

        // Capacité restante
        Gauge.builder("logback.async.queue.remaining", appender, AsyncAppender::getRemainingCapacity)
            .tag("appender", appenderName)
            .description("Remaining capacity in async queue")
            .register(registry);

        // Nombre de logs droppés
        Gauge.builder("logback.async.discarded", appender, AsyncAppender::getNumberOfElementsInQueue)
            .tag("appender", appenderName)
            .description("Number of discarded log events")
            .register(registry);
    }
}

Une alerte Prometheus sur logback.async.queue.remaining < 100 prévient des risques de perte de logs.

Intégration ELK Stack

Configuration Filebeat

Filebeat collecte les fichiers JSON et les envoie à Elasticsearch sans transformation.

yaml
# filebeat.yml
# Configuration Filebeat pour logs JSON Spring Boot
filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /var/log/*/application.json
    # Parse automatique du JSON
    json:
      keys_under_root: true
      overwrite_keys: true
      add_error_key: true
      message_key: message

processors:
  # Ajoute des métadonnées Kubernetes si disponible
  - add_kubernetes_metadata:
      host: ${NODE_NAME}
      matchers:
        - logs_path:
            logs_path: "/var/log/containers/"
  # Parse le timestamp
  - timestamp:
      field: "@timestamp"
      layouts:
        - '2006-01-02T15:04:05.000Z'
        - '2006-01-02T15:04:05.000-07:00'
      test:
        - '2026-03-27T10:15:32.456Z'

output.elasticsearch:
  hosts: ["elasticsearch:9200"]
  index: "logs-%{[service]}-%{+yyyy.MM.dd}"
  pipeline: "spring-boot-logs"

setup.template:
  name: "logs"
  pattern: "logs-*"

Pipeline Elasticsearch pour enrichissement

json
// PUT _ingest/pipeline/spring-boot-logs
{
  "description": "Enrichissement logs Spring Boot",
  "processors": [
    {
      "geoip": {
        "field": "client.ip",
        "target_field": "client.geo",
        "ignore_missing": true
      }
    },
    {
      "user_agent": {
        "field": "user_agent.original",
        "target_field": "user_agent",
        "ignore_missing": true
      }
    },
    {
      "set": {
        "field": "event.ingested",
        "value": "{{_ingest.timestamp}}"
      }
    },
    {
      "script": {
        "description": "Classify log level severity",
        "source": """
          def level = ctx.level;
          if (level == 'ERROR') ctx.severity = 4;
          else if (level == 'WARN') ctx.severity = 3;
          else if (level == 'INFO') ctx.severity = 2;
          else ctx.severity = 1;
        """
      }
    }
  ]
}

Bonnes pratiques pour la production

Informations à inclure systématiquement

Chaque log doit contenir les informations minimales pour le debugging et la corrélation.

StructuredLogger.javajava
// Helper pour logs structurés cohérents
package com.example.logging;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import java.util.Map;
import java.util.function.Supplier;

public final class StructuredLogger {

    private final Logger delegate;

    private StructuredLogger(Class<?> clazz) {
        this.delegate = LoggerFactory.getLogger(clazz);
    }

    public static StructuredLogger getLogger(Class<?> clazz) {
        return new StructuredLogger(clazz);
    }

    // Log avec contexte métier temporaire
    public void info(String message, Map<String, String> context) {
        try {
            context.forEach(MDC::put);
            delegate.info(message);
        } finally {
            context.keySet().forEach(MDC::remove);
        }
    }

    // Log avec supplier pour évaluation paresseuse
    public void debug(Supplier<String> messageSupplier, Map<String, String> context) {
        if (delegate.isDebugEnabled()) {
            try {
                context.forEach(MDC::put);
                delegate.debug(messageSupplier.get());
            } finally {
                context.keySet().forEach(MDC::remove);
            }
        }
    }

    // Log d'erreur avec contexte complet
    public void error(String message, Throwable t, Map<String, String> context) {
        try {
            context.forEach(MDC::put);
            delegate.error(message, t);
        } finally {
            context.keySet().forEach(MDC::remove);
        }
    }
}
java
// Utilisation dans le code métier
private static final StructuredLogger log = StructuredLogger.getLogger(PaymentService.class);

public void processPayment(Payment payment) {
    log.info("Processing payment", Map.of(
        "paymentId", payment.getId(),
        "amount", String.valueOf(payment.getAmount()),
        "currency", payment.getCurrency(),
        "method", payment.getMethod().name()
    ));
}

Informations sensibles à exclure

Les logs ne doivent jamais contenir de données personnelles ou sensibles.

SensitiveDataFilter.javajava
// Filtre de masquage des données sensibles
package com.example.logging.filter;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;

import java.util.regex.Pattern;

public class SensitiveDataFilter extends Filter<ILoggingEvent> {

    // Patterns de données sensibles à masquer
    private static final Pattern EMAIL_PATTERN =
        Pattern.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}");
    private static final Pattern CREDIT_CARD_PATTERN =
        Pattern.compile("\\b\\d{4}[- ]?\\d{4}[- ]?\\d{4}[- ]?\\d{4}\\b");
    private static final Pattern PASSWORD_PATTERN =
        Pattern.compile("(?i)(password|pwd|secret|token)[\"']?\\s*[:=]\\s*[\"']?[^\\s,}\"']+");
    private static final Pattern PHONE_PATTERN =
        Pattern.compile("\\+?\\d{1,3}[- ]?\\d{6,14}");

    @Override
    public FilterReply decide(ILoggingEvent event) {
        // Accepte tous les logs mais modifie le message
        // Note: pour un vrai masquage, utiliser un converter personnalisé
        return FilterReply.NEUTRAL;
    }

    // Méthode utilitaire pour masquer les données
    public static String maskSensitiveData(String input) {
        if (input == null) return null;

        String result = input;
        result = EMAIL_PATTERN.matcher(result).replaceAll("[EMAIL_MASKED]");
        result = CREDIT_CARD_PATTERN.matcher(result).replaceAll("[CARD_MASKED]");
        result = PASSWORD_PATTERN.matcher(result).replaceAll("$1=[REDACTED]");
        result = PHONE_PATTERN.matcher(result).replaceAll("[PHONE_MASKED]");

        return result;
    }
}
RGPD et conformité

Les logs contenant des données personnelles sont soumis au RGPD. Les adresses IP, emails, et identifiants utilisateurs nécessitent une politique de rétention et potentiellement un consentement.

Niveaux de log appropriés

LogLevelGuidelines.javajava
// Guide des niveaux de log appropriés
package com.example.logging;

public class LogLevelGuidelines {

    // ERROR : Échec nécessitant une intervention
    // - Exceptions non récupérables
    // - Échecs de transactions critiques
    // - Indisponibilité de services externes
    log.error("Payment gateway unreachable after 3 retries", exception);

    // WARN : Situation anormale mais gérée
    // - Retry en cours
    // - Dégradation de performance
    // - Ressources proches des limites
    log.warn("Database connection pool at 85% capacity");

    // INFO : Événements métier significatifs
    // - Début/fin de transactions
    // - Changements d'état importants
    // - Actions utilisateur clés
    log.info("Order {} shipped to customer {}", orderId, customerId);

    // DEBUG : Informations de diagnostic
    // - Détails d'exécution
    // - Valeurs de variables importantes
    // - Décisions de branchement
    log.debug("Cache miss for key {}, fetching from database", cacheKey);

    // TRACE : Détails très fins
    // - Entrée/sortie de méthodes
    // - Contenu complet des objets
    // - Boucles et itérations
    log.trace("Processing item {} of {}", index, total);
}

Tests et validation des logs

Test unitaire de la structure JSON

StructuredLoggingTest.javajava
// Tests de validation des logs structurés
package com.example.logging;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import static org.assertj.core.api.Assertions.assertThat;

class StructuredLoggingTest {

    private ListAppender<ILoggingEvent> listAppender;
    private Logger logger;
    private ObjectMapper objectMapper;

    @BeforeEach
    void setUp() {
        logger = (Logger) LoggerFactory.getLogger(StructuredLoggingTest.class);
        listAppender = new ListAppender<>();
        listAppender.start();
        logger.addAppender(listAppender);
        objectMapper = new ObjectMapper();
    }

    @Test
    void shouldIncludeMdcFieldsInLog() {
        // Given
        MDC.put("traceId", "test-trace-123");
        MDC.put("userId", "user-456");

        // When
        logger.info("Test message with MDC context");

        // Then
        ILoggingEvent event = listAppender.list.get(0);
        assertThat(event.getMDCPropertyMap())
            .containsEntry("traceId", "test-trace-123")
            .containsEntry("userId", "user-456");

        MDC.clear();
    }

    @Test
    void shouldLogExceptionWithStackTrace() {
        // Given
        Exception testException = new RuntimeException("Test error");

        // When
        logger.error("Operation failed", testException);

        // Then
        ILoggingEvent event = listAppender.list.get(0);
        assertThat(event.getThrowableProxy()).isNotNull();
        assertThat(event.getThrowableProxy().getMessage()).isEqualTo("Test error");
    }
}

Conclusion

Les logs structurés JSON transforment l'observabilité des applications Spring Boot :

Queryable : chaque champ devient filtrable dans Elasticsearch ou CloudWatch

Corrélable : le MDC propage les identifiants de trace entre services

Performant : l'appender asynchrone découple le logging du traitement

Sécurisé : le masquage des données sensibles assure la conformité RGPD

Intégré : compatibilité native avec ELK Stack, Datadog, Splunk

Alertable : les champs structurés permettent des règles d'alerte précises

Maintenable : le format JSON élimine les regex fragiles de parsing

Cette approche constitue le fondement de l'observabilité moderne, aux côtés des métriques (Micrometer) et du tracing distribué (OpenTelemetry).

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Tags

#spring boot logging
#logback json
#structured logs
#elk stack
#observabilité

Partager

Articles similaires