Как читать трейсы в Tempo¶
Шпаргалка по distributed tracing: где смотреть, как искать trace, как
коррелировать со слогами и метриками, как добавлять собственные spans.
Полный reference по инструментации — в
../conventions/observability.md.
Содержание¶
- Где трейсы
- Найти trace по trace_id
- Поиск без trace_id
- Структура trace
- Анализ slow request
- Correlation с logs
- Correlation с metrics
- Типовые сценарии
- Кастомные spans
- Storage tradeoffs
- Anti-patterns
- Связанные разделы
Где трейсы¶
Tempo — backend для distributed traces. UI — Grafana → Explore → datasource Tempo.
Трейсы собираются OpenTelemetry SDK в каждом сервисе, экспортируются
через OTLP в collector → Tempo. Настройка и context-propagation — в
../conventions/observability.md.
Найти trace по trace_id¶
Самый частый сценарий. trace_id уже есть:
- В JSON-записи лога (
trace_id— обязательное поле). - В error-ответе сервиса (если добавляется в header
Trace-Id). - В report от пользователя / поддержки.
В Tempo → Search → вкладка TraceQL → введи trace_id:
Или прямой URL: /explore?left=...&query=...trace_id="...".
Поиск без trace_id¶
TraceQL позволяет искать по атрибутам spans:
По сервису¶
По длительности¶
По тегам¶
{http.status_code=500}
{http.route="/v1/reviews" && duration > 500ms}
{db.system="postgresql" && duration > 200ms}
{messaging.system="kafka" && messaging.destination="kazmaps.review.review.created"}
По ошибке¶
По user_id¶
Структура trace¶
- Trace — полная операция, инициированная одним внешним запросом (HTTP call от клиента) или событием.
- Span — один шаг операции: HTTP handler, DB-запрос, Kafka publish, внешний HTTP call.
- Parent-child — spans образуют дерево. Корневой span — точка входа (обычно HTTP handler); дочерние — то, что происходит внутри.
- Attributes — структурные поля span'а (
http.method,db.statement,messaging.destination). - Events — точечные записи внутри span'а (
error.message,cache.hit).
Анализ slow request¶
Стандартный workflow:
- Нашёл медленный trace — открой waterfall.
- Каждый span имеет duration. Ищи длинный span с малым self-time (время без дочерних) — это работа самого span'а (не ожидание дочерних).
- Critical path — последовательность самых долгих non-parallel span'ов от корня до листа. Обычно оптимизация здесь даёт максимум.
- Parallel spans с разной длительностью — slow-one тормозит весь
родительский. Если parallel — это
errgroup.Wait, медленный ветвь удерживает всех.
Типичные bottleneck'и:
- DB span с
duration > 100ms. Смотриdb.statementв attributes — отсутствует индекс, or n+1. - External HTTP с
duration > 500ms. Проверь timeout'ы, circuit breaker, кэш. См.caching.md. - Kafka publish с
duration > 50ms. Проверь, публикуешь ли ты не через outbox — прямой publish из request-path это bug. См.../patterns/outbox.md. - Gap между span'ами. Если visual gap в waterfall — код между span'ами делает работу без инструментации. Добавь span.
Correlation с logs¶
Каждый trace ↔ логи связаны через trace_id.
- Из Tempo в Loki. В span → кнопка Logs for this span →
автоматически открывается Loki с фильтром
{service="<span.service>"} | json | trace_id="<trace>". - Из Loki в Tempo. В раскрытой JSON-записи кнопка View trace открывает trace в Tempo.
Rule of thumb: начинай с trace → спускайся в логи за деталями. Trace даёт временную структуру и где искать, логи — конкретные значения переменных в момент проблемы.
Correlation с metrics¶
Prometheus exemplars связывают график с конкретными trace'ами.
Если настроены exemplars (HistogramOpts с NativeHistogramBucketFactor
и OTel exemplar hook в коде):
- Grafana dashboard → график
histogram_quantile(0.99, http_request_duration_seconds)→ spike. - Клик по точке spike'а → появляется exemplar — конкретный request в этот момент.
- Кнопка рядом — прыжок в Tempo.
Так spike на графике превращается в кликабельный trace за два клика.
Типовые сценарии¶
Slow endpoint¶
- Grafana dashboard → latency panel → p99 вырос.
- Клик на exemplar → trace.
- Waterfall → самый длинный span.
- Если span — DB: смотри
db.statement, добавь индекс / перепиши запрос. - Если external HTTP: проверь downstream сервис, timeout, кэш.
- Прыжок в Loki по
trace_idза контекстом (args, user_id).
Distributed flow (Kafka)¶
Publisher кладёт traceparent в Kafka-envelope (см.
../conventions/events.md), consumer'ы в
других сервисах подхватывают контекст и продолжают trace.
Result: один trace покрывает путь HTTP → service A → outbox → forwarder → Kafka → service B → DB. Смотришь всю цепочку в одном окне.
Debug-применение: сообщение не дошло до DB в сервисе B.
- Найди trace по HTTP-request в сервисе A.
- Посмотри, дошёл ли span
kafka.publish(forwarder). - Есть ли span сервиса B? Если нет — сообщение ещё в Kafka, проблема в consumer.
- Если есть — смотри ошибку в span'е B, смотри
status=error, прыжок в Loki сервиса B.
Race condition¶
Два trace с похожим shape за близкое время, но один проходит, другой падает. Ставь рядом в Grafana Compare traces (или открой в параллельных вкладках). Сравни timing span'ов — часто видно, что во втором trace какой-то span сдвинут на миллисекунды и попал в гонку.
Кастомные spans¶
Автоматическая инструментация (otelhttp, otelpgx, otelsarama
через Watermill) покрывает большую часть. Добавляй собственные span'ы
только для значимых бизнес-шагов, которых авто-инструментация не
видит.
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
)
var tracer = otel.Tracer("kazmaps.review.service")
func (s *Service) ProcessReview(ctx context.Context, id int64) error {
ctx, span := tracer.Start(ctx, "ProcessReview")
defer span.End()
span.SetAttributes(attribute.Int64("review.id", id))
if err := s.validate(ctx, id); err != nil {
span.RecordError(err)
return err
}
return nil
}
- Имя tracer'а — уникально на пакет:
"kazmaps.<service>.<package>". - Имя span'а — CamelCase, короткое, без аргументов (
ProcessReview, неProcessReview(id=42)). Аргументы — в attributes. span.RecordError(err)— стандартный способ пометить span failed.
Storage tradeoffs¶
Tempo оптимизирован под поиск по trace_id, не под полнотекстовый
поиск:
- Поиск по attributes работает, но тяжелее, чем в Loki.
- Retention ограничена (обычно 7–14 дней). Для long-term — экспорт в отдельное хранилище.
- Sampling в collector'е: не все trace'ы хранятся, часть сэмплируется. При расследовании редкого инцидента — учитывай, что конкретный trace мог быть отброшен до Tempo.
Anti-patterns¶
- Span на каждую строчку кода. Тысячи span'ов на trace, Tempo режет, UI тормозит. Инструментируй только значимые границы (handler, service-method, external call).
- PII в attributes.
user.email,request.token,password— запрещено. Attributes тоже подчиняются правилам../conventions/logging.mdи../conventions/security.md. - Trace для health-checks.
/healthz,/readyz,/metrics— шум. Отключай в middleware (sampling 0 для этих path'ов). - Ручное управление
trace_id. Генерировать trace_id самому и пробрасывать вручную — ломает W3C-propagation. Используй стандартные propagator'ы, они сами разберутся. - Span вместо лога. Не кладите в
span.AddEventто, что должно быть вslog.Info. Events в span — для точечных временных вех, не для подробных dump'ов.
Связанные разделы¶
../conventions/observability.md— setup OpenTelemetry, propagators, semantic conventions../read-logs.md— Loki и прыжок в логи из trace.../conventions/events.md— Kafka trace propagation через envelope metadata.