Observability 3.0. Логи и трейсы - Loki, Tempo и OpenTelemetry
Это третья статья из серии Observability 3.0 простыми словами. В предыдущей разобрали метрики и дашборды. Теперь логи и трейсы.
Метрики говорят что случилось. CPU вырос, ошибок стало больше, латентность подскочила. Но они не говорят почему. Для этого нужны логи и трейсы.
Зачем логи, если есть метрики
Простой пример. Grafana показывает - количество ошибок 500 выросло в 10 раз за последние 5 минут. Это метрика. Полезно, но что дальше?
Без логов вы знаете что сломалось (HTTP 500), но не знаете почему. С логами вы видите конкретное сообщение
2026-03-07 14:32:05 ERROR [order-service] Failed to connect to payment gateway: connection timeout after 30s
Теперь понятно. Сервис заказов не может достучаться до платёжного шлюза. Это уже конкретика, с которой можно работать.
Loki. Хранилище логов
Loki - система для хранения и поиска логов. Разработана Grafana Labs, поэтому идеально встраивается в стек.
Чем отличается от ELK
Если вы слышали про ELK (Elasticsearch + Logstash + Kibana), то Loki - его лёгкая альтернатива. Главное отличие в том, что ELK индексирует весь текст каждого лога. Loki индексирует только метки (labels) - название приложения, среда, сервер.
Что это значит на практике
- Loki потребляет в разы меньше памяти и диска
- Loki проще в настройке и обслуживании
- Поиск по меткам быстрый, полнотекстовый поиск медленнее чем в ELK
- Для 90% задач L2-поддержки скорости Loki хватает с запасом
Как устроен
Логи попадают в Loki через сборщик - Promtail или Grafana Alloy (новая замена Promtail).
Сборщик работает так
- Читает файлы логов на сервере, например
/var/log/nginx/access.log - Добавляет метки вроде
{app="nginx", env="production", host="web-01"} - Отправляет в Loki
Loki сжимает логи и хранит их. Когда вы ищете, Loki сначала фильтрует по меткам (это быстро), а потом ищет текст внутри подходящих потоков.
LogQL. Язык запросов для логов
У Loki свой язык запросов, похожий на PromQL.
{app="api-gateway"} |= "error"
Все логи приложения api-gateway, содержащие слово error.
{app="api-gateway"} |= "error" | json | response_time > 5000
Те же логи, но только где время ответа больше 5 секунд. Оператор | json автоматически разбирает JSON-формат логов.
{env="production"} |~ "timeout|refused|unreachable"
Все логи из рабочей среды, где встречается timeout, refused или unreachable. Оператор |~ это поиск по регулярному выражению.
sum(rate({app="api-gateway"} |= "error" [5m])) by (host)
Количество ошибок в секунду по каждому хосту за последние 5 минут. Полезно, чтобы понять - проблема на одном сервере или на всех.
Promtail и Grafana Alloy
Promtail - оригинальный сборщик логов для Loki. Простой, надёжный, делает свою работу.
Но есть нюанс. Grafana Labs прекратила активную разработку Promtail с февраля 2025 года. Замена - Grafana Alloy.
Alloy умеет всё то же, что Promtail, плюс
- Собирает не только логи, но и метрики и трейсы (как OpenTelemetry)
- Единый конфигурационный формат
- Активно развивается
Если ставите с нуля - берите Alloy. Если Promtail уже работает, спешить с миграцией не надо. Он продолжает работать в режиме долгосрочной поддержки (LTS).
Зачем трейсы, если есть логи
Логи отвечают на вопрос - что произошло внутри одного сервиса. Но в микросервисной архитектуре один запрос пользователя проходит через 5-10 сервисов. Лог каждого сервиса - кусок пазла. Трейс собирает пазл целиком.
Пример. Пользователь жалуется - оформление заказа заняло 8 секунд.
Без трейсов. Вы смотрите логи каждого сервиса по отдельности. API Gateway - 200мс. Сервис заказов - 300мс. Сервис оплаты - непонятно. Где потерялись остальные 7.5 секунд? Неизвестно.
С трейсами. Открываете трейс конкретного запроса и видите водопад
API Gateway |████| 200ms
→ Order Service | |██████| 300ms
→ Inventory | |██| 100ms
→ Payment | |████████████████████████| 7200ms ← вот оно
→ Bank API | |████████████████████████| 7100ms
Сразу видно. Платёжный шлюз ответил за 7.1 секунды. Проблема не в ваших сервисах, а во внешнем API банка.
Tempo. Хранилище трейсов
Tempo - хранилище трейсов от Grafana Labs. Работает по тому же принципу, что Loki. Минимум индексации, максимум эффективности.
Как устроен
- Приложения инструментируются через OpenTelemetry (добавляется библиотека в код)
- При каждом запросе создаётся уникальный trace ID - идентификатор, который проходит через все сервисы
- Каждый сервис отправляет свой span (отрезок) в Tempo
- Tempo собирает все спаны с одним trace ID и складывает в единый трейс
Что вы увидите
В Grafana трейсы отображаются как диаграмма-водопад (waterfall). Каждая строка - один сервис. Ширина полоски - время выполнения. Красным подсвечиваются ошибки.
Вы кликаете на конкретный спан и видите
- Длительность
- HTTP-статус
- Теги - URL, метод, параметры
- Логи, привязанные к этому спану
Связка с метриками и логами
Главная сила Tempo - интеграция с Grafana. Видите в метриках всплеск латентности? Кликаете View traces и видите конкретные медленные запросы. Кликаете на спан - видите привязанные логи из Loki. Всё связано.
Эта связка метрики -> трейсы -> логи называется корреляция. Именно она отличает observability от простого мониторинга.
OpenTelemetry. Универсальный стандарт
OpenTelemetry (OTel) - не инструмент, а стандарт. Он описывает, как приложения должны генерировать и отправлять метрики, логи и трейсы.
Зачем нужен стандарт
Без OTel каждый инструмент говорит на своём языке. Prometheus хочет метрики в одном формате, Jaeger хочет трейсы в другом, Fluentd хочет логи в третьем. Разработчику приходится ставить три разные библиотеки.
С OTel всё проще. Одна библиотека, один формат, один коллектор. Данные идут в OTel Collector, а он уже отправляет их куда нужно. Метрики в Prometheus, логи в Loki, трейсы в Tempo.
OTel Collector
Промежуточное звено между приложениями и системами хранения. Принимает данные, обрабатывает (фильтрует, обогащает, батчит) и отправляет дальше.
Зачем нужен посредник
- Приложения не зависят от конкретной системы хранения. Решили перейти с Jaeger на Tempo - меняете конфигурацию коллектора, а не код приложений
- Коллектор может сжимать данные, отбрасывать ненужные, добавлять метки
- Одна точка конфигурации вместо десяти
Проверка работы коллектора
Если OTel Collector установлен и вам надо проверить, работает ли он
curl http://localhost:13133/
Если ответ содержит "status": "Server available" - коллектор работает.
Если метрики пустые (эндпоинт /metrics отвечает, но данных нет) - приложение не отправляет данные в коллектор. Проверяйте настройки приложения.
Как всё работает вместе
Полный поток данных
- Приложение инструментировано через OpenTelemetry. При каждом запросе создаётся трейс, пишутся логи, обновляются метрики
- OTel Collector принимает всё и раскидывает. Метрики в Prometheus, логи в Loki, трейсы в Tempo
- Grafana показывает всё в едином интерфейсе с корреляцией
Или без OTel (проще, но менее гибко)
- Node Exporter / cAdvisor отдают метрики напрямую в Prometheus
- Promtail / Alloy отправляет логи в Loki
- Grafana показывает
Первый вариант для приложений (нужна инструментация кода). Второй для инфраструктуры (работает без изменений в коде).
Docker Compose. Добавляем логи и трейсы
Если вы уже подняли базовый стек из статьи про метрики, добавьте в docker-compose.yml следующие сервисы
loki:
image: grafana/loki:latest
ports:
- "3100:3100"
volumes:
- loki-data:/loki
restart: unless-stopped
promtail:
image: grafana/promtail:latest
volumes:
- /var/log:/var/log:ro
- ./promtail-config.yml:/etc/promtail/config.yml
command: -config.file=/etc/promtail/config.yml
restart: unless-stopped
tempo:
image: grafana/tempo:latest
ports:
- "3200:3200"
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
volumes:
- tempo-data:/var/tempo
command: -config.file=/etc/tempo/tempo.yaml
restart: unless-stopped
Добавьте volumes в секцию volumes
volumes:
loki-data:
tempo-data:
Минимальный promtail-config.yml
server:
http_listen_port: 9080
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: system
static_configs:
- targets:
- localhost
labels:
job: varlogs
__path__: /var/log/*.log
После запуска добавьте в Grafana два новых Data Source
- Loki → URL
http://loki:3100 - Tempo → URL
http://tempo:3200
Теперь в Grafana работает корреляция. На графике метрик можно кликнуть и перейти к логам за тот же период.
Практический сценарий для L2
Вот как выглядит разбор инцидента с полным стеком.
Ситуация. Алерт в Slack - латентность order-service выше 2 секунд
Шаг 1. Метрики (Grafana + Prometheus) Открываете дашборд order-service. Видите, что латентность p99 прыгнула с 200мс до 3 секунд в 14:25.
Шаг 2. Трейсы (Tempo)
Кликаете View traces за период 14:25-14:30. Видите трейсы, где спан database-query занимает 2.8 секунды вместо обычных 50мс.
Шаг 3. Логи (Loki)
Кликаете на спан, смотрите привязанные логи. Видите WARN: connection pool exhausted, waiting for free connection. База не виновата - закончились соединения в пуле.
Шаг 4. Корреляция Смотрите метрики PostgreSQL Exporter за тот же период. Активных соединений 100 из 100 (максимум). Обычно 20-30. Кто-то открывает соединения и не закрывает.
Эскалация. Сервис order-service - латентность выросла из-за исчерпания пула соединений к PostgreSQL. Активных соединений 100 из 100 с 14:25. В логах connection pool exhausted. Вероятная причина - утечка соединений. Нужна проверка кода.
Весь разбор - 5 минут.
Часть серии Observability 3.0 простыми словами. На основе материалов Cumhur M. Akkaya.


