Gymterview
middle

Как организовать centralized logging в микросервисах?

Centralized logging — подход, при котором логи всех сервисов собираются в единое хранилище для поиска, анализа и корреляции. В микросервисной архитектуре без централизованного логирования невозможно расследовать проблемы, затрагивающие несколько сервисов.

Основные стеки

Аспект ELK (Elastic Stack) Grafana Loki
Путь данных Приложение -> Logstash/Filebeat -> Elasticsearch -> Kibana Приложение -> Promtail/Alloy -> Loki -> Grafana
Индексирование Полнотекстовый индекс (каждое слово) Индекс только по лейблам
Стоимость хранения Высокая (индекс занимает много места) Низкая (сжатые chunks)
Поиск Быстрый по любому полю Быстрый по лейблам, медленнее по тексту
Масштабирование Сложное (кластер Elasticsearch) Простое (object storage S3/GCS)
Экосистема Зрелая, множество интеграций Тесная интеграция с Grafana, Tempo, Prometheus

Structured logging (структурированные логи)

Вместо текстовых логов используйте JSON — легче парсить, фильтровать и анализировать:

Пример
// НЕ делайте так:
log.info("Order " + orderId + " created for user " + userId + " with amount " + amount);
// "Order 12345 created for user 42 with amount 9999" — парсить сложно

// Делайте так (structured):
log.info("Order created",
    kv("orderId", orderId),
    kv("userId", userId),
    kv("amount", amount));
// {"message":"Order created","orderId":"12345","userId":"42","amount":9999}
Настройка structured logging (Spring Boot + Logback)
# application.yml (Spring Boot 3.4+)
logging:
  structured:
    format:
      console: ecs       # Elastic Common Schema
<!-- logback-spring.xml -->
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <includeMdcKeyName>traceId</includeMdcKeyName>
            <includeMdcKeyName>spanId</includeMdcKeyName>
            <customFields>{"service":"order-service","env":"production"}</customFields>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

Результат:

{
  "@timestamp": "2026-04-22T10:30:00.123Z",
  "@version": "1",
  "message": "Order created",
  "logger_name": "com.example.OrderService",
  "thread_name": "http-nio-8080-exec-1",
  "level": "INFO",
  "traceId": "abc123def456",
  "spanId": "789xyz",
  "service": "order-service",
  "env": "production",
  "orderId": "12345",
  "userId": "42"
}

Correlation ID / Trace ID

Для связывания логов из разных сервисов используется единый идентификатор:

Пример
// Micrometer Tracing автоматически добавляет traceId в MDC
// В Loki (LogQL):
// {service="order-service"} | json | traceId="abc123def456"
Кастомный Correlation ID фильтр (если нет трейсинга)
@Component
public class CorrelationIdFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                     HttpServletResponse response,
                                     FilterChain chain) throws ServletException, IOException {
        String correlationId = request.getHeader("X-Correlation-ID");
        if (correlationId == null) {
            correlationId = UUID.randomUUID().toString();
        }
        MDC.put("correlationId", correlationId);
        response.setHeader("X-Correlation-ID", correlationId);
        try {
            chain.doFilter(request, response);
        } finally {
            MDC.remove("correlationId");
        }
    }
}

Сбор логов в Kubernetes

В Kubernetes стандартный подход — логирование в stdout, а сборщик (DaemonSet) читает логи контейнеров:

Пример
┌──────────────────────────────────────────────┐
│              Kubernetes Node                  │
│                                               │
│  ┌───────────┐  ┌───────────┐  ┌───────────┐ │
│  │ Pod A     │  │ Pod B     │  │ Pod C     │ │
│  │ stdout→   │  │ stdout→   │  │ stdout→   │ │
│  └─────┬─────┘  └─────┬─────┘  └─────┬─────┘ │
│        │              │              │        │
│        ▼              ▼              ▼        │
│  ┌─────────────────────────────────────────┐  │
│  │  /var/log/containers/*.log               │  │
│  └──────────────┬──────────────────────────┘  │
│                 │                              │
│  ┌──────────────▼──────────────┐              │
│  │  Promtail / Fluentd / Alloy │ (DaemonSet) │
│  └──────────────┬──────────────┘              │
└─────────────────┼────────────────────────────┘
                  │
                  ▼
          ┌───────────────┐
          │  Loki / ELK   │
          └───────────────┘

LogQL — язык запросов Loki

Пример
# Все логи сервиса
{service="order-service"}

# Логи с ошибками
{service="order-service"} |= "ERROR"
{service="order-service"} | json | level="ERROR"

# Конкретный traceId (поиск по всем сервисам)
{service=~".+"} | json | traceId="abc123def456"

# Количество ошибок в секунду
sum(rate({service="order-service"} | json | level="ERROR" [5m]))

Управление уровнями логирования через Actuator

Пример
# Временно включить DEBUG для конкретного пакета (без перезапуска)
curl -X POST http://localhost:9090/actuator/loggers/com.example.service.OrderService \
  -H 'Content-Type: application/json' \
  -d '{"configuredLevel": "DEBUG"}'

# Вернуть обратно
curl -X POST http://localhost:9090/actuator/loggers/com.example.service.OrderService \
  -H 'Content-Type: application/json' \
  -d '{"configuredLevel": null}'

Важное

  • Логи в stdout — стандарт для Kubernetes. Не пишите в файлы внутри контейнера.
  • Structured JSON logging — обязательно в микросервисах. Текстовые логи невозможно эффективно парсить.
  • traceId в каждой записи — позволяет найти все логи конкретного запроса по всем сервисам.
  • Log levels: DEBUG — только для разработки, INFO — нормальная работа, WARN — потенциальная проблема, ERROR — ошибка требует внимания.
  • Retention policy: логи нельзя хранить вечно. Настройте автоматическое удаление (7 дней для DEBUG, 30 для ERROR, 90 для audit).

Частые ошибки

  • Логирование чувствительных данных: пароли, токены, номера карт в логах — нарушение PCI DSS и GDPR. Используйте маскирование.
  • Отсутствие structured logging: log.info("Something happened with " + obj) — невозможно фильтровать.
  • Логирование в файл внутри контейнера: файл теряется при перезапуске пода, если не смонтирован volume.
  • Слишком много DEBUG-логов в production: создаёт огромный объём данных и увеличивает стоимость хранения.
  • Нет корреляции: логи есть, но без traceId невозможно связать события из разных сервисов.

Как используется в 2026

  • Grafana Loki — основной выбор для новых проектов благодаря низкой стоимости и интеграции с Grafana stack.
  • Grafana Alloy (замена Promtail + Grafana Agent) — единый агент для сбора метрик, логов и трейсов.
  • OpenTelemetry Logs — стандартный способ отправки логов через OTel Collector, с автоматической корреляцией с трейсами.
  • Spring Boot 3.4+ Structured Logging — нативная поддержка JSON/ECS формата логов без дополнительных библиотек.
  • Log-based metrics — Loki и Elasticsearch позволяют создавать метрики из логов.
  • AI-powered log analysis — автоматическое обнаружение паттернов ошибок и группировка похожих событий.

На собеседовании: начните с обоснования необходимости centralized logging в микросервисах (невозможно SSH на каждый под). Обязательно упомяните structured logging (JSON) и корреляцию через traceId. Покажите знание хотя бы одного стека (ELK или Loki). Частая ошибка — забыть про retention policy и маскирование чувствительных данных.