Как организовать 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 и маскирование чувствительных данных.