Когда «что-то тормозит» или «сыпятся 5xx», большинство команд по привычке прыгают между тремя мирами: метрики (Prometheus), логи (Loki) и трассировки (Grafana Tempo). Самое неприятное не в том, что инструментов много, а в том, что они живут отдельно: алерт показывает деградацию, логов слишком много, а трассировку нужного запроса ещё надо уметь найти.
Ниже — практичная схема корреляции: чтобы из алерта по метрикам переходить к логам, а из логов — к конкретному trace по trace_id. Это сильно ускоряет triage (первичную диагностику инцидента) и превращает observability в повторяемый runbook, а не в «красивые графики».
Что даёт Tempo в связке с Loki и Prometheus
Prometheus отвечает на вопрос «что сломалось и насколько»: рост латентности, всплеск ошибок, насыщение ресурсов. Но метрика редко говорит «почему» и «в каком запросе».
Loki отвечает на вопрос «что происходило по шагам»: сообщения приложения, stack trace, причины ошибок, контекст. Но при высокой нагрузке логов много, и поиск нужной цепочки событий может занять десятки минут.
Grafana Tempo отвечает на вопрос «как именно прошёл конкретный запрос»: какие сервисы участвовали, где была задержка, какой downstream дал ошибку. Это и есть distributed tracing.
Ключевой момент: ценность даёт не «наличие трейсинга», а быстрые переходы:
- из алерта (метрики) к соответствующему сегменту логов (Loki);
- из конкретной строки лога к trace (Tempo) по
trace_id; - из trace обратно к метрикам и логам по атрибутам и времени.
Эта «склейка» и называется correlation. В результате triage превращается в короткий, повторяемый сценарий.
Целевая архитектура: минимальный набор компонентов
Есть два уровня: «минимально рабочий» и «продакшен-осмысленный». Для большинства небольших и средних проектов достаточно первого, но если хотите нормальный tail-sampling и аккуратную маршрутизацию телеметрии — лучше сразу предусмотреть коллектор.
Минимально рабочая схема
- Prometheus собирает метрики сервисов.
- Loki принимает логи (через promtail или другой агент).
- Tempo принимает трассы.
- Grafana подключена к трём источникам данных и умеет прыгать между ними.
- Приложения инструментированы через OpenTelemetry (SDK или авто-инструментация).
Продакшен-осмысленная схема (рекомендация)
- OpenTelemetry Collector как единая точка приёма телеметрии (трейсы/метрики/логи) и маршрутизации.
- Отдельные политики sampling (в приложении и/или в коллекторе).
- Единые поля корреляции:
trace_id,span_id,service.name,deployment.environment.
Самая частая причина «Tempo есть, но толку мало» — отсутствие дисциплины в корреляционных полях. Если в логах нет traceID, быстрый путь от ошибки к trace не построится.
Если вы размещаете Grafana-стек рядом с приложением и хотите контролируемые ресурсы и сеть (особенно для Collector, Loki/Tempo и их дисков), удобнее поднимать всё на отдельной VDS и уже там централизовать телеметрию.

База корреляции: откуда берётся traceID и как его не потерять
trace_id рождается в момент начала трассировки (обычно на входе в систему: HTTP/gRPC gateway, ingress, edge-сервис). Дальше он должен пройти сквозь все компоненты запроса: прокси, приложение, очередь, downstream-сервисы.
Чтобы корреляция работала, обеспечьте три вещи:
- Контекст трассировки корректно передаётся между сервисами (W3C Trace Context: заголовок
traceparentи опциональноtracestate). - Логи содержат
trace_id(и желательноspan_id) в отдельном поле или стабильно парсимом формате. - Метрики позволяют сузить поиск до нужного сервиса/эндпоинта/инстанса, чтобы не «прочёсывать» всю систему.
Практическое правило: лог «ошибка в обработчике платежей» почти бесполезен. Лог «ошибка в обработчике платежей, trace_id=…, request_id=…» превращает triage в быстрый, последовательный процесс.
Инструментирование: OpenTelemetry для distributed tracing
Самый устойчивый путь — OpenTelemetry. Он задаёт стандарт на уровне SDK, протокола передачи (OTLP) и семантических атрибутов. В контексте Tempo это важно по двум причинам:
- Tempo отлично принимает OTLP (HTTP/gRPC) напрямую или через OpenTelemetry Collector.
- Вы получаете предсказуемые поля вроде
service.name, которые удобно использовать в Grafana для фильтрации и навигации.
Рекомендуемая практика — задавать:
service.name— неизменное имя сервиса (например,billing-api);service.version— версия/релиз (полезно при раскатках и сравнении регрессий);deployment.environment—prod/stage(и далее как у вас принято).
Если у вас микросервисы, не экономьте время на унификацию: разнобой в названиях сервисов и окружений ломает фильтры и усложняет triage.
Логи: как связать Loki с Tempo через traceID
Loki — это система, где эффективность строится на лейблах и разумной структуре логов. Для корреляции с Tempo важнее всего, чтобы trace_id было:
- либо отдельным полем в JSON-логе (идеально);
- либо стабильным фрагментом строки, который можно вытащить парсером на стороне агента или запросом.
Практическая схема:
- Приложение пишет structured logs (JSON) и добавляет поля
trace_id,span_id. - Агент доставки (например, promtail) парсит JSON и поднимает часть полей в labels (очень аккуратно), а остальное оставляет в теле лога.
- В Grafana настраивается извлечение
trace_idиз логов и переход в Tempo по найденному идентификатору.
Критически важно: не делайте trace_id лейблом Loki. У traceID огромная кардинальность, это быстро превратит Loki в дорогой и медленный индексатор. TraceID должен жить в теле лога и использоваться для точечного поиска и клика в Tempo.
Если хотите глубже разобраться с тем, какие поля действительно стоит поднимать в labels и как собирать пайплайны без взрыва кардинальности, держите отдельный разбор: лейблы и pipeline-обработка в Loki.
Какие поля логировать для эффективного triage
Минимальный набор для веб-сервисов:
timestamp,level,messageserviceилиservice.nametrace_id,span_idhttp.method,http.routeили понятный endpointstatusилиhttp.status_codeduration_ms(если логируете окончания запросов)
Дополнительно по ситуации — request_id (если он уже есть в системе), идентификаторы джоб/очередей, и аккуратно — пользовательские идентификаторы (с учётом требований безопасности и приватности).
Метрики: как Prometheus помогает быстро сузить область поиска
Prometheus обычно остаётся «входной точкой» в инцидент: алерт срабатывает по latency/error rate. Чтобы следующий шаг был быстрым, метрики должны быть:
- привязаны к сервису (
job,instance,service); - иметь разрезы по ключевым маршрутам/операциям (
route,handler) там, где кардинальность контролируема; - коррелироваться по времени с логами и trace (синхронизация времени на хостах обязательна).
На практике triage выглядит так: алерт показал рост p95 на /api/payments в billing-api. Вы фильтруете логи Loki по сервису и временному диапазону, находите ошибки/таймауты и берёте trace_id проблемного запроса. Дальше Tempo покажет, где именно потеряли время.
Настройка correlation в Grafana: быстрые переходы «метрики → логи → trace»
В Grafana корреляция складывается из двух частей:
- связка источников данных Loki, Tempo и Prometheus в Explore;
- правила извлечения
trace_idиз логов и переход в Tempo по найденному идентификатору.
Реализация зависит от формата логов. Если это JSON, обычно проще: trace_id — отдельное поле. Если это текст, договоритесь о формате (например, trace_id=), чтобы регулярка была надёжной.
Проверка работоспособности простая:
- Откройте Loki Explore и найдите строки с
trace_idв нужном окне времени. - Перейдите по
trace_idв Tempo. - В Tempo найдите проблемный span (БД, внешний API, очередь, DNS/TLS и т. п.).
- Вернитесь в Loki и посмотрите соседние логи этого сервиса вокруг времени проблемного участка.
Если переход из логов «работает», но trace не находится, чаще всего виноваты sampling/retention или смешанные окружения без явной маркировки (например, нет
deployment.environment).

Sampling и стоимость: как не утонуть в трассировках
Distributed tracing легко «съедает» ресурсы, если включить 100% sampling на высоконагруженном фронте. Для triage обычно не нужно хранить все trace: нужно хранить достаточно, чтобы быстро находить репрезентативные проблемные случаи.
Практичные подходы:
- Head-based sampling (на входе): например, 1–10% запросов.
- Tail-based sampling (после завершения trace): сохраняете 100% ошибочных и медленных, а остальное — по проценту.
- Отдельные правила для эндпоинтов: health-check почти всегда можно исключить.
Чтобы tail-based sampling работал корректно, обычно нужен OpenTelemetry Collector или другой компонент, который видит trace целиком и может принять решение «сохранять/не сохранять».
Сценарий triage: от алерта до root cause за 5–10 минут
Ниже — алгоритм, который удобно закрепить как короткий runbook для дежурной смены.
1) Старт: алерт Prometheus
Смотрим: какой сервис, какой маршрут/операция, какой тип деградации (p95/p99, error rate, saturation). Ограничиваем окно времени, например последние 15 минут.
2) Переход в Loki: подтверждаем симптом
Фильтруем логи по сервису и времени, ищем ошибки и маркеры деградации:
- 5xx/exception;
- timeouts upstream/downstream;
- признаки деградации БД/очередей;
- ошибки подключения (DNS, TLS, pool exhausted).
Берём 1–3 характерных примера и выписываем их trace_id.
3) Переход в Tempo: где «пропало время»
Открываем trace по trace_id и смотрим критический путь:
- какой span самый долгий;
- какой downstream вызов вернул ошибку;
- есть ли ретраи, fan-out, очереди;
- корректны ли таймауты на уровне клиента.
4) Возврат к метрикам: проверяем гипотезу
Если Tempo показывает задержку в БД — возвращаемся к метрикам БД/пула соединений. Если проблема во внешнем API — смотрим метрики ретраев, таймаутов и rate limit. Связка полезна тем, что каждый шаг подтверждает предыдущий: метрика дала направление, лог дал конкретный случай, trace показал структуру и «узкое место», метрика закрыла гипотезу системно.
Типовые ошибки внедрения и как их избежать
TraceID не попадает в логи
Это проблема №0. Решение: настроить логгер на добавление полей из контекста трассировки. Обычно это делается через middleware/handler, который кладёт trace/span в MDC/контекст и сериализует их в JSON.
Слишком высокая кардинальность в Loki
Нельзя превращать в labels то, что уникально почти для каждого запроса: trace_id, userID, requestID. Лейблы должны быть «грубыми»: сервис, окружение, уровень, кластер, namespace. Остальное — в теле лога.
Нет единых имён сервисов и окружений
Если один и тот же сервис где-то называется billing, где-то billing-api, а где-то payments, то фильтры и дашборды будут «дырявыми». Договоритесь о naming convention сразу и зафиксируйте в шаблонах деплоя.
Sampling отрезает именно то, что нужно
При чисто процентном head-based sampling можно не сохранить редкую, но критичную ошибку. Для triage хорошо работает правило: ошибки и сильно медленные запросы сохраняем чаще (tail-based или отдельные правила).
Мини-чеклист готовности: «можно дежурить по этой системе»
- В каждом сервисе включён OpenTelemetry и задан
service.name. - В логах есть
trace_id(и желательноspan_id). - Grafana умеет переходить из Loki в Tempo по traceID.
- Есть базовые RED-метрики в Prometheus (rate, errors, duration) по ключевым сервисам.
- Настроены retention и sampling так, чтобы проблемные trace не исчезали слишком быстро.
- Время синхронизировано на хостах, иначе корреляция по таймлайну ломается.
Итог
Связка Grafana Tempo, Loki и Prometheus — это про скорость и воспроизводимость triage. Prometheus даёт сигнал и масштаб проблемы, Loki даёт контекст и конкретную ошибку, Tempo показывает структуру запроса и место деградации. А correlation по trace_id превращает это в единый маршрут расследования, который реально экономит часы во время инцидента.
Если внедряете observability поэтапно, начинайте с дисциплины: единые имена сервисов, trace_id в логах, базовые метрики. Дальше добавляйте Tempo и правила sampling — и вы получите «APM-эффект» без монолитного APM-вендора, на открытых компонентах.
Для отдельных задач мониторинга (проверки доступности HTTP/TCP/ICMP, сертификатов, внешних зависимостей) удобно дополнять схему синтетическими проверками; см. разбор: настройка blackbox-exporter для Prometheus.


