Структуроване логування Spring Boot 2026: JSON-логи для production з Logback та OpenTelemetry

Повний посібник зі структурованого логування Spring Boot 4.x. Нативна підтримка JSON, starter OpenTelemetry, MDC tracing та інтеграція з ELK Stack для спостережуваності у production.

Структуроване логування у Spring Boot з Logback і JSON

Традиційні текстові логи у production швидко стають некерованими. Сотні інстансів генерують тисячі рядків на секунду, тож пошук конкретної помилки перетворюється на кошмар. Структуровані JSON-логи змінюють цю ситуацію докорінно: кожна подія стає придатною для запитів і автоматичного аналізу.

Ключове

Spring Boot 4.x (побудований на Spring Framework 7) нативно підтримує структуроване JSON-логування з форматами ECS, Logstash та GELF. Новий spring-boot-starter-opentelemetry забезпечує уніфіковану спостережуваність без зовнішніх залежностей.

Чому варто переходити на структуроване логування в Spring Boot

Обмеження традиційних текстових логів

Типовий текстовий лог виглядає так:

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

У production цей формат породжує низку проблем. Виокремлення конкретної інформації потребує складних і крихких регулярних виразів. Кореляція між сервісами вимагає суворих угод, які кожна команда інтерпретує по-своєму. Інструменти аналізу, такі як Elasticsearch, важко ефективно індексують ці неструктуровані рядки.

Переваги формату JSON

Та сама подія у JSON миттєво стає придатною для роботи:

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
}

Кожне поле стає фільтрованим і агрегованим. Запит до Elasticsearch миттєво знаходить усі замовлення понад 100 € за останні п'ятнадцять хвилин. Дашборди Kibana візуалізують тренди без ручного парсингу. Ця тема особливо актуальна у питаннях для співбесід по Spring Boot, де розуміння патернів спостережуваності у production відрізняє досвідчених кандидатів.

Нативна конфігурація структурованого логування Spring Boot 4.x

Увімкнення структурованих JSON-логів

Spring Boot 4.x, побудований на Spring Framework 7, надає зрілу підтримку структурованого логування через властивість logging.structured. Цей підхід не вимагає додаткових залежностей та інтегрується безпосередньо з 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

Формат ECS (Elastic Common Schema) гарантує пряму сумісність з Elasticsearch і Kibana без додаткових налаштувань.

Налаштування полів JSON

Щоб додати бізнес-поля до кожного логу, Spring Boot дозволяє налаштовувати додаткові атрибути.

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

Ці поля з'являються у кожному рядку логу та полегшують фільтрацію за командою чи регіоном у дашбордах.

OpenTelemetry Starter для повної спостережуваності

Новий стандарт у Spring Boot 4

Spring Boot 4.0 представив spring-boot-starter-opentelemetry, замінивши складну конфігурацію з багатьма залежностями, що існувала раніше. Ця єдина залежність забезпечує спостережуваність, незалежну від постачальника, включаючи traces, метрики та кореляцію логів.

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>

Цей starter включає OpenTelemetry API, Micrometer tracing bridge та OTLP експортери. Spring Cloud Sleuth тепер вважається legacy, а OpenTelemetry є галузевим стандартом для розподіленого трасування.

Конфігурація OpenTelemetry зі структурованими логами

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:-}]"

Ідентифікатори trace та span автоматично вставляються в MDC, корелюючи логи з розподіленими traces між сервісами. Детальніше про налаштування моніторингу у production див. посібник Spring Boot Actuator з Micrometer та Prometheus.

Повідомлення про deprecation

ZipkinWithOpenTelemetryTracingAutoConfiguration позначено як deprecated і буде видалено у Spring Boot 4.2. Міграція на нативні OTLP експортери рекомендована для сумісності.

Класична конфігурація Logback з JSON Encoder

Logstash Encoder для розширеного налаштування

Для розширених потреб налаштування або при міграції зі старих версій Spring Boot, Logstash Logback Encoder 9.0 залишається доступним. Зверніть увагу, що версія 9.0 потребує Jackson 3.0 та мінімум Java 17.

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>

Повна конфігурація Logback

Файл logback-spring.xml забезпечує повний контроль над форматом виводу.

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>

Ця конфігурація вмикає JSON-логи лише у production, залишаючи читабельні логи у середовищі розробки.

MDC для розподіленого трасування

Поширення трасувального контексту

MDC (Mapped Diagnostic Context) збагачує кожен лог контекстною інформацією, як-от ідентифікатори запиту або трасування.

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

Кожен лог, виданий під час обробки запиту, автоматично містить ці ідентифікатори.

Використання MDC у бізнес-коді

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

Отриманий JSON-лог містить усю необхідну для дебагу інформацію:

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"
}

Готовий до співбесід з Spring Boot?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Асинхронне логування для продуктивності

Налаштування пулу потоків

У production синхронні записи логів впливають на затримку запитів. Асинхронний appender відокремлює логування від основного потоку.

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>
Критичне виправлення помилки у Spring Boot 4.1.1

Spring Boot 4.1.1 виправив критичну проблему, через яку невдале JSON-кодування могло пошкодити наступну подію логу в тому ж потоці (#51371). Оновлення з 4.0.x або 4.1.0 рекомендується для уникнення тихого пошкодження логів.

Метрики системи логування

Моніторинг самої системи логування запобігає тихій втраті логів.

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

Алерт Prometheus на logback.async.queue.remaining < 100 попереджає про ризик втрати логів.

Інтеграція з ELK Stack

Конфігурація Filebeat

Filebeat збирає JSON-файли і надсилає їх до Elasticsearch без перетворень.

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 для збагачення

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;"
      }
    }
  ]
}

Найкращі практики у production для логування Spring Boot

Інформація, яку слід додавати систематично

Кожен лог має містити мінімум інформації, потрібної для дебагу та кореляції.

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()
    ));
}

Чутлива інформація, яку слід виключати

Логи ніколи не повинні містити персональні чи конфіденційні дані.

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;
    }
}

Доцільні рівні логів

РівеньВипадок використанняПриклади
ERRORЗбій, що вимагає втручанняНепоправні винятки, критичні помилки транзакцій, недоступність зовнішнього сервісу
WARNНенормальна, але оброблена ситуаціяТриває повторна спроба, деградація продуктивності, ресурси наближаються до лімітів
INFOЗначущі бізнес-подіїПочаток/кінець транзакції, важливі зміни стану, ключові дії користувача
DEBUGДіагностична інформаціяДеталі виконання, важливі значення змінних, рішення про розгалуження
TRACEДуже детальна інформаціяВхід/вихід з методів, повний вміст об'єктів, цикли та ітерації

Тестування та валідація структурованих логів

Юніт-тести структури 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");
    }
}

Патерни інтеграційного тестування, що добре працюють зі структурованим логуванням, описані у посібнику з інтеграційного тестування Testcontainers Spring Boot.

Джерела

Чек-лист структурованого логування для Spring Boot 4.x

  • Нативне структуроване логування з форматами ECS, Logstash або GELF потребує лише конфігурації logging.structured.format
  • spring-boot-starter-opentelemetry замінює legacy-конфігурації Sleuth спостережуваністю, незалежною від постачальника
  • MDC автоматично поширює ідентифікатори trace між сервісами
  • Асинхронні appendери з neverBlock=true запобігають впливу логування на затримку запитів
  • Logstash Logback Encoder 9.0 потребує Jackson 3.0 та Java 17
  • Spring Boot 4.1.1 виправив критичну помилку JSON-кодування, що впливала на конкурентні потоки
  • Маскування чутливих даних забезпечує відповідність GDPR у production-логах
  • Метрики ємності асинхронної черги дозволяють налаштувати алерти до втрати логів

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Щоденний виклик

Чи знайдеш ти помилку в Spring Boot?

Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Anthony Fillion-Maillet

Автор:

Anthony Fillion-Maillet

Засновник SharpSkill

Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.

Оновлено 22 серпня 2026 р.

Теги

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

Поділитися

Пов'язані статті