Пошаговый разбор расследования ошибок в микросервисах: граф сервисов, хронология трейсов, корреляция логов и структурированная отладка на примере Uptrace
Введение
В микросервисной архитектуре один падающий эндпоинт может скрывать проблему в совершенно другом сервисе. В этой статье я покажу пошаговый процесс расследования реального инцидента: от обнаружения 100% error rate до точной причины сбоя — и всё это менее чем за минуту.
Мы будем использовать Uptrace — OpenTelemetry-native платформу для трейсинга и мониторинга. Все примеры основаны на реальном demo-приложении с микросервисами.
Что вы узнаете:
Как использовать service graph для поиска bottleneck
Техники фильтрации spans для быстрого углублённого анализа
Как восстановление хронологии помогает найти узкое место
Корреляция трейсов и логов для анализа первопричины
От расследования к предотвращению через мониторинг
Симптом: проблемный эндпоинт
Представим ситуацию: пользователи жалуются, что рекомендации не загружаются. Открываем APM dashboard и сразу видим проблему:
Endpoint: GET /api/recommendations Error rate: 100% 🔴 Latency p90: 10.6ms Rate: 1.9 spans/min

100% error rate — это критично. Каждый запрос падает. Но почему?
Phase 1: Service Graph — где проблема?
Первый шаг — понять где именно в цепочке сервисов происходит сбой. Для этого используем service graph.
Что такое service graph?
Service graph визуализирует, как сервисы вызывают друг друга. Каждый узел — это сервис, каждое ребро — вызов между сервисами.
Пример нашего графа:
[frontend] ---> [recommendation] ---> [product-catalog] ✅ 🔴 100% errors ❓

Граф сервисов: красные узлы (frontend, recommendation) сигнализируют об ошибках. Зелёные (product-reviews, cart) работают в норме. На рёбрах видна задержка и процент ошибок
Уже видно, что:
frontend делает запросы (красный узел — есть проблемы)
recommendation возвращает ошибки 🔴
Зелёные узлы (product-reviews, cart) работают нормально ✅
Проблема где-то внутри или дальше по цепочке
Incoming vs Outgoing perspective
Service graph можно смотреть в двух режимах:
Incoming — кто вызывает сервис (зависимости на сервис)
Outgoing — какие зависимости ждёт сервис
Переключаясь между ними, видим:
Incoming: frontend → recommendation (6.1ms duration, errors)
Outgoing: recommendation → ??? (здесь и проблема)
Вывод Phase 1: Проблема в recommendation — он не может достучаться до своих зависимостей.
Phase 2: Анализ проблемных spans
Теперь нужно посмотреть на конкретные запросы. Для этого переходим к spans.
Фильтрация: убираем шум
В production-системе миллионы spans. Нужна точная фильтрация:
Шаг 1: Фокус на одном сервисе
service_name = frontend
Шаг 2: Только server spans (обработанные запросы)
_kind = server
Шаг 3: Только ошибки
_status_code = error
Теперь видим только проблемные запросы от frontend.
Aggregations: понимаем масштаб
Группируем spans по операциям и смотрим aggregations:
Aggregations: - perMin(count()) # сколько запросов в минуту - p99(_dur_ms) # 99-й процентиль latency - _error_rate # процент ошибок Group by: - _group_id # группировка одинаковых операций
Результат:
Operation | Rate | p99 latency | Status |
|---|---|---|---|
GET /api/recommendations | 2.1/min | 16ms | 🔴 Errors |
POST /api/checkout | 1.4/min | 23ms | 🔴 Errors |
GET /api/products/{productId} | 11/min | 5.7ms | 🔴 Errors |

Spans → Groups: три активных фильтра (service_name, kind, status_code) сузили список до 5 групп. Агрегации perMin(count()), p99(_dur_ms) и error rate показывают масштаб проблемы
Видим, что проблема затрагивает несколько операций, но GET /api/recommendations показывает стабильный error pattern. Давайте углубимся в эту операцию и посмотрим конкретные запросы.
Проблема изолирована — можно сосредоточиться на конкретных операциях!
Phase 2.5: Выбор конкретного span
Кликаем на одну из строк GET /api/recommendations в таблице Groups. Открывается детальная панель с информацией об этом конкретном запросе.

Клик на GET /api/recommendations открывает боковую панель. Вкладка ATTRS показывает все структурированные атрибуты в формате key-value. Кнопка VIEW TRACE ведёт к полной хронологии запроса
Выбор конкретного span открывает детальный view с его атрибутами в виде structured key-value pairs. Это позволяет легко фильтровать и искать проблемы.
Что мы видим в атрибутах:
http_status_code: 500— подтверждает ошибку сервераhttp_route: /api/recommendations— конкретный endpointhttp_target— полный URL с query-параметрамиhost_name: play-all-in-one— на каком хосте запущен сервисnext_span_name— следующий span в цепочке вызовов
Это structured logging в действии — каждое поле имеет имя и тип, что делает отладку намного эффективнее, чем работа с обычными текстовыми логами.
Теперь кликаем VIEW TRACE, чтобы увидеть полную картину запроса через все микросервисы.
Phase 3: Trace timeline — где узкое место?
После клика на VIEW TRACE открывается waterfall timeline — полная история одного запроса через все микросервисы.
Что такое trace?
Trace — это полная история одного запроса через все микросервисы. Визуализируется как waterfall timeline.
Пример нашего trace:
GET /api/recommendations [9.5ms total] ├─ user_get_recommendations (load-generator) [0.7ms] │ └─ GET http://frontend-proxy:8080/... [1.3ms] │ └─ Ingress (frontend-proxy) [0.2ms] │ └─ router frontend egress [0.8ms] │ └─ GET http://frontend-proxy:8080/... [0.3ms] │ └─ GET /api/recommendations (frontend) [6.4ms] 🔴 │ └─ rpc:recommendation [failures]

Waterfall view trace: путь запроса от load-generator через proxy до frontend. Последний span (6.4ms) — явный виновник задержки и ошибок
Timeline analysis:
Total duration: 9.5ms
Load generator: 0.7ms (инициация запроса)
HTTP calls через proxy: ~2ms
Frontend processing: 6.4ms — здесь основное время! 🔴
Из них значительная часть уходит на rpc:recommendation (26% от total)
Видны ошибки (красные маркеры)
Вывод Phase 3: Bottleneck в GET /api/recommendations на frontend (6.4ms из 9.5ms). Запрос проходит через несколько переходов (load-generator → proxy → frontend), но основное время и ошибки происходят на финальном этапе при вызове recommendation service.
Детальный просмотр проблемного span
Кликаем на самый длинный span GET /api/recommendations (6.4ms) в waterfall. Открывается детальная панель справа с полной информацией об этом span.

Панель атрибутов самого долгого span: статус error, http_status_code = 500, длительность 6.4ms. Видны все метаданные сервиса: хост, ОС, версия библиотеки Next.js
Этот span показывает где именно происходит ошибка — frontend обрабатывает запрос /api/recommendations, но получает 500 error.
Ключевые атрибуты:
http_status_code: 500— сервер вернул internal server errorhttp_target— полный URL с параметромproductIds=L9ECAV7KIMnext_span_name: GET /api/recommendations— следующий вызов в цепочкеnext_span_type: BaseServer.handleRequest— тип обработчика запросаotel_library_name: next.js— используется Next.js framework
Теперь нужно понять почему этот span падает. Для этого переходим к логам, связанным с этим трейсом.
Phase 4: Logs — первопричина
Трейс показал где проблема. Теперь нужно понять почему. Для этого смотрим логи, связанные с трейсом.
Корреляция трейсов и логов
В Uptrace трейсы и логи связаны через trace_id. Не нужно отдельно искать логи по времени или сервису — достаточно, находясь в waterfall view, кликнуть на вкладку LOGS & ERRORS (7), и все логи этого конкретного запроса появятся автоматически.

Вкладка «LOGS & ERRORS (7)»: все 7 записей относятся к одному trace_id. Повторяющееся сообщение «DNS resolution failed for product-catalog:3550» с gRPC статусом UNAVAILABLE (код 14) — это и есть первопричина
Что видим в логах:
Повторяющееся сообщение об ошибке появляется много раз с одинаковым содержанием:
Exception: _InactiveRpcError of RPC that terminated with: status = StatusCode.UNAVAILABLE details = "DNS resolution failed for product-catalog:3550; C-ares status is not ARES_SUCCESS qtype=A name=product-catalog is_balancer=0; DNS server returned general failure" grpc_status: 14 grpc_message: "DNS resolution failed for product-catalog:3550"
Также видим HTTP access log, показывающий результат:
GET /api/recommendations?productIds=L9ECAV7KIM HTTP/1.1 Status: 500 User-Agent: python-requests/2.32.4
Каждая запись лога связана с трейсом через trace_id. Клик по иконке ссылки открывает полные структурированные атрибуты записи в формате key-value
Первопричина найдена!
Проблема: DNS не может разрезолвить product-catalog:3550
Технические детали:
gRPC Status: UNAVAILABLE (code 14)
DNS Query: A record для
product-catalogDNS Resolver: C-ares (асинхронный DNS resolver)
Result: "DNS server returned general failure"
Возможные причины:
Сервис product-catalog не запущен
Неправильный DNS record в Kubernetes/service mesh
Network policy блокирует доступ к DNS
Неправильный hostname в конфиге (должен быть product-catalog.namespace.svc.cluster.local)
DNS server перегружен или недоступен
Время от симптома до первопричины: ~60 секунд ⏱️
Phase 5: Предотвращение через monitoring
Расследование закончено — мы нашли первопричину. В production-среде следующим шагом было бы исправление (исправить DNS record, перезапустить сервис, обновить конфигурацию). Но важно не только исправить текущую проблему, но и предотвратить повторение. Для этого создаём монитор.
От trace к monitor:
Возвращаемся на вкладку TRACE
Кликаем на проблемный span GET /api/recommendations (6.4ms) в waterfall
Справа открывается детальная панель span
Кликаем на dropdown MONITOR
Выбираем Monitor p99 duration

Кнопка MONITOR в панели span открывает меню из 7 вариантов. Выбираем «Monitor p99 duration» — Uptrace автоматически заполнит все поля формы из атрибутов текущего span
После выбора Monitor p99 duration Uptrace автоматически создаёт форму с предзаполненными полями из span.
Что автоматически заполнено:
Monitor: httpserver:frontend > GET /api/recommendations p99 duration Metric: uptrace_tracing_spans Aggregation: p99($spans) Filter: - _group_id = "8037161693486813802" (это ID группы spans для данной операции) Alert threshold: p99 latency > 15ms Check interval: last 5 points (5 minutes) Notification: Email to EVERYONE

Зелёная зона на графике показывает допустимый диапазон (до 15ms). Когда p99 latency превышает этот порог — монитор создаёт уведомление автоматически.
Теперь если p99 latency превысит 15ms — команда получит автоматический alert и сможет среагировать до того, как проблема станет критичной. Монитор можно настроить на отправку уведомлений в Slack, Telegram, PagerDuty или по email.

Подсказки
1. Structured filtering
Не смотри на все spans сразу. Фильтруй:
service_name— один сервис_kind = server— только обработанные запросыstatuscode = error— только ошибки
2. Timeline thinking
Trace timeline — это история запроса. Ищи:
Самые длинные spans (узкое место)
Failed spans (где падает)
Unexpected calls (лишние вызовы)
3. Log-trace correlation
Логи без контекста бесполезны. trace_id связывает:
Один лог → полный request flow
Structured attributes → фильтрация и поиск
4. Aggregations matter
Не смотри на один span. Aggregations показывают:
perMin(count())— частота проблемыp99(_dur_ms)— worst case latencyerror_rate— % падений
5. От расследования к предотвращению
Расследование не заканчивается на исправлении. Мониторы превращают знание в автоматизацию.
Заключение
Distributed tracing — это не просто «смотреть на логи». Это системный подход к расследованию:
Service graph — найти проблемный сервис
Span filtering — изолировать проблемные запросы
Trace timeline — понять узкое место
Logs correlation — первопричина
Monitors — предотвращение
С правильными инструментами (OpenTelemetry + Uptrace) путь от симптома до решения занимает меньше минуты вместо часов отладки.
Что дальше?
Попробуй Uptrace: play.uptrace.dev
Open-source APM на базе OpenTelemetry: uptrace.dev/get/hosted/open-source-apm
Установи OTEL Collector: uptrace.dev/opentelemetry/collector
Изучи best practices: uptrace.dev/opentelemetry
Вопросы? Комментарии? Пишите ниже! 👇
