Logs estruturados no Spring Boot em 2026: JSON para produção com Logback e OpenTelemetry

Guia completo de logs estruturados no Spring Boot 4.x. Suporte JSON nativo, starter OpenTelemetry, tracing MDC e integração ELK Stack para observabilidade em produção.

Logging estruturado em Spring Boot com Logback e JSON

Logs tradicionais em formato texto rapidamente se tornam ingerenciáveis em produção. Com centenas de instâncias gerando milhares de linhas por segundo, procurar um erro específico vira um pesadelo. Logs estruturados em JSON transformam essa situação ao tornar cada evento consultável e analisável de forma automática.

Ponto-chave

O Spring Boot 4.x (construído sobre o Spring Framework 7) suporta nativamente logs estruturados em JSON com formatos ECS, Logstash e GELF. O novo spring-boot-starter-opentelemetry oferece observabilidade unificada sem dependências externas.

Por que adotar logs estruturados no Spring Boot

Limitações dos logs em texto plano

Um log de texto típico tem este aspecto:

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

Esse formato apresenta diversos problemas em produção. Extrair informações específicas exige expressões regulares complexas e frágeis. A correlação entre serviços demanda convenções rígidas que cada equipe interpreta à sua maneira. Ferramentas de análise como o Elasticsearch enfrentam dificuldades para indexar essas strings não estruturadas com eficiência.

Vantagens do formato JSON

O mesmo evento em JSON torna-se imediatamente utilizável:

json
{
  "@timestamp": "2026-08-22T10: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
}

Cada campo torna-se filtrável e agregável. Uma consulta no Elasticsearch encontra instantaneamente todos os pedidos acima de 100€ dos últimos quinze minutos. Os dashboards do Kibana visualizam tendências sem parsing manual. Essa competência é particularmente relevante nas questões de entrevista Spring Boot, onde a compreensão dos padrões de observabilidade em produção diferencia candidatos seniores.

Configuração nativa de logs estruturados no Spring Boot 4.x

Ativando logs JSON estruturados

O Spring Boot 4.x, construído sobre o Spring Framework 7, introduz suporte maduro para logging estruturado por meio da propriedade logging.structured. Essa abordagem dispensa qualquer dependência adicional e integra-se diretamente com o Logback 1.5.38.

yaml
# application.yml
# Native structured logging configuration for Spring Boot 4.x
logging:
  structured:
    # Output format: ecs (Elastic), logstash, gelf
    format:
      console: ecs
      file: ecs
  file:
    name: /var/log/app/application.log
  level:
    root: INFO
    com.example: DEBUG

O formato ECS (Elastic Common Schema) garante compatibilidade direta com Elasticsearch e Kibana sem configuração adicional.

Personalizando os campos JSON

Para adicionar campos de negócio em cada log, o Spring Boot permite configurar atributos extras.

yaml
# application.yml
# Custom fields in structured logs
logging:
  structured:
    format:
      console: ecs
    ecs:
      # Service information added to every log
      service:
        name: ${spring.application.name}
        version: ${app.version:1.0.0}
        environment: ${spring.profiles.active:default}
        node-name: ${HOSTNAME:unknown}
LoggingConfig.javajava
// Programmatic configuration for additional fields
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
            // Adds static fields to all logs
            .addStaticField("team", "backend")
            .addStaticField("region", System.getenv("AWS_REGION"))
            // Customizes exception formatting
            .setIncludeStacktrace(true)
            .setStacktraceMaxLength(5000);
    }
}

Esses campos aparecem em cada linha de log e facilitam a filtragem por equipe ou região nos dashboards.

OpenTelemetry Starter para observabilidade completa

O novo padrão no Spring Boot 4

O Spring Boot 4.0 introduziu o spring-boot-starter-opentelemetry, substituindo a configuração multi-dependências que existia anteriormente. Essa única dependência fornece observabilidade vendor-neutral incluindo traces, métricas e correlação de logs.

xml
<!-- pom.xml -->
<!-- OpenTelemetry starter for Spring Boot 4.x -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-opentelemetry</artifactId>
</dependency>
<!-- Log correlation for Logback -->
<dependency>
    <groupId>io.opentelemetry.instrumentation</groupId>
    <artifactId>opentelemetry-logback-appender-1.0</artifactId>
    <version>2.21.0-alpha</version>
</dependency>

Esse starter inclui a API do OpenTelemetry, o bridge do Micrometer tracing e os exportadores OTLP. O Spring Cloud Sleuth agora é considerado legacy, sendo o OpenTelemetry o padrão da indústria para tracing distribuído.

Configuração OpenTelemetry com logs estruturados

yaml
# application.yml
# OpenTelemetry configuration with structured logging
spring:
  application:
    name: order-service
    group: commerce

otel:
  exporter:
    otlp:
      endpoint: http://otel-collector:4317
      protocol: grpc
  resource:
    attributes:
      service.namespace: production
      deployment.environment: prod

logging:
  structured:
    format:
      console: ecs
  pattern:
    level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"

Os identificadores de trace e span são automaticamente injetados no MDC, correlacionando os logs com os traces distribuídos entre serviços. Para mais informações sobre configurações de monitoramento em produção, consulte o guia Spring Boot Actuator com Micrometer e Prometheus.

Aviso de depreciação

ZipkinWithOpenTelemetryTracingAutoConfiguration está depreciada e programada para remoção no Spring Boot 4.2. A migração para exportadores OTLP nativos é recomendada para compatibilidade futura.

Configuração clássica do Logback com encoder JSON

Logstash Encoder para personalização avançada

Para necessidades de personalização avançada ou ao migrar de versões anteriores do Spring Boot, o Logstash Logback Encoder 9.0 permanece disponível. A versão 9.0 requer Jackson 3.0 e Java 17 como mínimo.

xml
<!-- pom.xml -->
<!-- Dependency for JSON logging with Logback (Jackson 3 required) -->
<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>9.0</version>
</dependency>

Configuração Logback completa

O arquivo logback-spring.xml oferece controle total sobre o formato de saída.

xml
<!-- src/main/resources/logback-spring.xml -->
<!-- Logback configuration for structured JSON logs -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- Spring Boot properties -->
    <springProperty scope="context" name="appName" source="spring.application.name" defaultValue="app"/>
    <springProperty scope="context" name="appVersion" source="app.version" defaultValue="1.0.0"/>

    <!-- JSON console appender for production -->
    <appender name="JSON_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <!-- Custom fields added to every log -->
            <customFields>{"service":"${appName}","version":"${appVersion}"}</customFields>
            <!-- Includes MDC (tracing context) -->
            <includeMdcKeyName>traceId</includeMdcKeyName>
            <includeMdcKeyName>spanId</includeMdcKeyName>
            <includeMdcKeyName>userId</includeMdcKeyName>
            <includeMdcKeyName>requestId</includeMdcKeyName>
            <!-- ISO8601 timestamp format -->
            <timestampPattern>yyyy-MM-dd'T'HH:mm:ss.SSSZ</timestampPattern>
            <!-- Complete stack traces -->
            <throwableConverter class="net.logstash.logback.stacktrace.ShortenedThrowableConverter">
                <maxDepthPerThrowable>30</maxDepthPerThrowable>
                <maxLength>4096</maxLength>
                <shortenedClassNameLength>36</shortenedClassNameLength>
                <rootCauseFirst>true</rootCauseFirst>
            </throwableConverter>
        </encoder>
    </appender>

    <!-- Rolling JSON file appender -->
    <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>

    <!-- Text appender for development -->
    <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 by Spring profile -->
    <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>

Essa configuração ativa logs JSON apenas em produção, preservando logs legíveis em desenvolvimento.

MDC para tracing distribuído

Propagação do contexto de trace

O MDC (Mapped Diagnostic Context) enriquece cada log com informações de contexto, como identificadores de requisição ou de trace.

TracingFilter.javajava
// Filter for automatic trace context injection
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 {

    // Standard MDC keys for 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 {
            // Retrieve or generate trace identifiers
            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");

            // Inject into MDC to appear in all 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);
            }

            // Propagate to responses for inter-service chaining
            response.setHeader("X-Trace-Id", traceId);
            response.setHeader("X-Request-Id", requestId);

            filterChain.doFilter(request, response);

        } finally {
            // Clean MDC after each request
            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);
    }
}

Cada log emitido durante o processamento da requisição conterá automaticamente esses identificadores.

Uso do MDC no código de negócio

OrderService.javajava
// Business service with enriched contextual logging
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) {
        // Add business information to MDC context
        MDC.put("orderId", request.getOrderId());
        MDC.put("customerId", request.getCustomerId());

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

            // Business logic...
            Order order = processOrder(request);

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

            return order;

        } catch (Exception e) {
            // Exception appears with full MDC context
            log.error("Failed to create order", e);
            throw e;
        } finally {
            // Clean business keys added
            MDC.remove("orderId");
            MDC.remove("customerId");
        }
    }
}

O log JSON resultante contém todas as informações necessárias para o debugging:

json
{
  "@timestamp": "2026-08-22T10: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"
}

Pronto para mandar bem nas entrevistas de Spring Boot?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Logging assíncrono para desempenho

Configuração do thread pool

Em produção, gravações síncronas de logs impactam a latência das requisições. O appender assíncrono desacopla o logging da thread principal.

xml
<!-- logback-spring.xml -->
<!-- High-performance asynchronous appender configuration -->
<appender name="ASYNC_JSON" class="ch.qos.logback.classic.AsyncAppender">
    <!-- Pending log buffer size -->
    <queueSize>1024</queueSize>
    <!-- Never block the calling thread -->
    <neverBlock>true</neverBlock>
    <!-- Threshold before dropping DEBUG/TRACE logs -->
    <discardingThreshold>20</discardingThreshold>
    <!-- Include caller information (expensive) -->
    <includeCallerData>false</includeCallerData>
    <!-- Actual appender for writing -->
    <appender-ref ref="JSON_FILE"/>
</appender>

<springProfile name="prod">
    <root level="INFO">
        <appender-ref ref="ASYNC_JSON"/>
    </root>
</springProfile>
Correção crítica no Spring Boot 4.1.1

O Spring Boot 4.1.1 corrigiu um problema crítico onde uma falha no encode JSON poderia corromper o próximo evento de log na mesma thread (#51371). Recomenda-se a atualização a partir de 4.0.x ou 4.1.0 para evitar corrupção silenciosa de logs.

Métricas do sistema de logging

Monitorar o próprio sistema de logging evita perda silenciosa de logs.

LoggingMetrics.javajava
// Exposing Logback metrics 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);

        // Iterate through appenders to find AsyncAppenders
        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();

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

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

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

Um alerta Prometheus em logback.async.queue.remaining < 100 avisa sobre risco de perda de logs.

Integração com ELK Stack

Configuração do Filebeat

O Filebeat coleta os arquivos JSON e os envia ao Elasticsearch sem transformação.

yaml
# filebeat.yml
# Filebeat configuration for Spring Boot JSON logs
filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /var/log/*/application.json
    # Automatic JSON parsing
    json:
      keys_under_root: true
      overwrite_keys: true
      add_error_key: true
      message_key: message

processors:
  # Add Kubernetes metadata if available
  - add_kubernetes_metadata:
      host: ${NODE_NAME}
      matchers:
        - logs_path:
            logs_path: "/var/log/containers/"
  # Parse timestamp
  - timestamp:
      field: "@timestamp"
      layouts:
        - '2006-01-02T15:04:05.000Z'
        - '2006-01-02T15:04:05.000-07:00'
      test:
        - '2026-08-22T10: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 para enriquecimento

json
{
  "description": "Spring Boot logs enrichment",
  "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;"
      }
    }
  ]
}

Melhores práticas para produção no Spring Boot

Informações para incluir sistematicamente

Cada log deve conter um mínimo de informações para debugging e correlação.

StructuredLogger.javajava
// Helper for consistent structured logs
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 with temporary business context
    public void info(String message, Map<String, String> context) {
        try {
            context.forEach(MDC::put);
            delegate.info(message);
        } finally {
            context.keySet().forEach(MDC::remove);
        }
    }

    // Log with supplier for lazy evaluation
    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);
            }
        }
    }

    // Error log with full context
    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
// Usage in business code
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()
    ));
}

Informações sensíveis para excluir

Logs nunca devem conter dados pessoais ou sensíveis.

SensitiveDataFilter.javajava
// Sensitive data masking utility
package com.example.logging.filter;

import java.util.regex.Pattern;

public final class SensitiveDataFilter {

    // Sensitive data patterns to mask
    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 SensitiveDataFilter() {}

    // Utility method to mask data
    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]");

        return result;
    }
}

Níveis de log apropriados

NívelCaso de usoExemplos
ERRORFalha que requer intervençãoExceções não recuperáveis, falhas críticas de transações, indisponibilidade de serviços externos
WARNSituação anormal mas tratadaTentativa em andamento, degradação de desempenho, recursos próximos dos limites
INFOEventos de negócio significativosInício/fim de transações, mudanças de estado importantes, ações-chave do usuário
DEBUGInformações de diagnósticoDetalhes de execução, valores de variáveis importantes, decisões de ramificação
TRACEDetalhes muito finosEntrada/saída de métodos, conteúdo completo de objetos, loops e iterações

Testes e validação dos logs estruturados

Testes unitários sobre a estrutura JSON

StructuredLoggingTest.javajava
// Structured log validation tests
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 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;

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

    @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");
    }
}

Para padrões de testes de integração que funcionam bem com logs estruturados, consulte o guia de integração do Testcontainers com Spring Boot.

Fontes

Checklist de logs estruturados para Spring Boot 4.x

  • O logging estruturado nativo com formato ECS, Logstash ou GELF requer apenas a configuração logging.structured.format
  • O spring-boot-starter-opentelemetry substitui as configurações legacy do Sleuth por observabilidade vendor-neutral
  • O MDC propaga automaticamente os identificadores de trace entre serviços
  • Os appenders assíncronos com neverBlock=true evitam impactar a latência das requisições
  • O Logstash Logback Encoder 9.0 requer Jackson 3.0 e Java 17
  • O Spring Boot 4.1.1 corrigiu um bug crítico de codificação JSON que afetava threads concorrentes
  • O mascaramento de dados sensíveis garante conformidade com LGPD/GDPR em logs de produção
  • As métricas sobre capacidade da fila async permitem alertar antes da perda de logs

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Desafio do dia

Você saberia encontrar o bug em Spring Boot?

Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador da SharpSkill

Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.

Atualizado em 22 de agosto de 2026

Tags

#spring boot logging
#logback json
#structured logs
#elk stack
#observability

Compartilhar

Artigos relacionados