Ідея за 30 секунд
Distributed tracing показує один run не в межах одного сервісу, а по всьому ланцюгу викликів.
У мульти-агентних системах запит часто проходить через gateway, runtime, tools, черги і LLM-провайдерів.
Distributed tracing пов'язує ці кроки через trace_id, span_id і parent_span_id, тому поведінку системи можна бачити end-to-end.
Основна проблема
Коли агент працює в кількох сервісах, логи зазвичай розкидані в різних місцях.
Окремо видно помилки gateway, окремо — tool service, окремо — agent runtime.
Але ці події не зв'язані між собою як один run.
Але без спільного trace context важко зрозуміти, що це один і той самий run.
У результаті навіть простий інцидент стає довгим розслідуванням:
- неясно, у якому сервісі з'явилась затримка;
- незрозуміло, де загубився контекст;
- складно пов'язати retries між сервісами;
- важко відтворити повний шлях проблемного run.
Саме тому для мульти-агентних систем потрібен distributed tracing, а не лише локальний трейсинг усередині одного runtime.
Як це працює
Distributed tracing використовує ту саму модель trace і span, але в межах кількох сервісів.
trace— увесь шлях одного запиту через всі сервісиspan— конкретна операція в одному сервісі
У реальних системах ці поля зазвичай базуються на OpenTelemetry (OTel):
trace_id— спільний ідентифікатор для всього шляхуspan_id— ідентифікатор поточного крокуparent_span_id— зв'язок з батьківським крокомservice_name— де саме виконано крокoperation_name— що саме робив сервіс
Щоб трейс не обривався, trace context треба передавати між сервісами разом із кожним викликом.
Найчастіше це роблять через headers (traceparent) або metadata у повідомленнях черги.
Як виглядає distributed trace
Найпростіше зрозуміти distributed tracing на прикладі одного запиту.
trace_id: tr_7a31
user_query: "Find vendor invoices for March"
gateway span g1 parent=- 18ms status=ok
agent_runtime span a1 parent=g1 240ms status=ok
tool_service span t1 parent=a1 410ms status=ok
agent_runtime span a2 parent=a1 130ms status=ok
llm_provider span l1 parent=a2 690ms status=ok
stop_reason: completed
Такий трейс показує:
- повний шлях між сервісами;
- який сервіс дав найбільшу затримку;
- де саме обірвався контекст (якщо це сталося);
- які спани були батьківськими, а які дочірніми.
Коли використовувати
Distributed tracing не завжди потрібен.
Якщо система монолітна і весь run живе в одному процесі, часто вистачає локального трейсингу.
Але distributed tracing стає критичним, коли:
- один запит проходить через кілька сервісів;
- у workflow є черги або async-воркери;
- кілька агентів обмінюються подіями;
- потрібен точний розбір latency і retries між сервісами.
Приклад реалізації
Нижче — спрощений приклад, як передавати trace context між gateway і worker-сервісом.
У прикладі нижче використані спрощені заголовки (x-trace-id, x-parent-span-id), щоб показати сам принцип propagation.
У production-системах зазвичай використовують стандартний W3C-заголовок traceparent (через OpenTelemetry), який автоматично передає trace context між сервісами.
import contextvars
import logging
import time
import uuid
logger = logging.getLogger("distributed-tracing")
trace_id_ctx = contextvars.ContextVar("trace_id", default=None)
span_id_ctx = contextvars.ContextVar("span_id", default=None)
def start_span(service_name, operation_name, parent_span_id=None):
span_id = str(uuid.uuid4())
started_at = time.time()
logger.info(
"span_started",
extra={
"trace_id": trace_id_ctx.get(),
"span_id": span_id,
"parent_span_id": parent_span_id,
"service_name": service_name,
"operation_name": operation_name,
},
)
return span_id, started_at
def finish_span(service_name, operation_name, span_id, started_at, status, parent_span_id=None, error=None):
logger.info(
"span_finished",
extra={
"trace_id": trace_id_ctx.get(),
"span_id": span_id,
"parent_span_id": parent_span_id,
"service_name": service_name,
"operation_name": operation_name,
"status": status,
"latency_ms": int((time.time() - started_at) * 1000),
"error": error,
},
)
def inject_context(headers):
headers["x-trace-id"] = trace_id_ctx.get() or ""
headers["x-parent-span-id"] = span_id_ctx.get() or ""
def extract_context(headers):
incoming_trace_id = headers.get("x-trace-id") or str(uuid.uuid4())
incoming_parent_span_id = headers.get("x-parent-span-id")
trace_token = trace_id_ctx.set(incoming_trace_id)
return incoming_parent_span_id, trace_token
def gateway_handle_request():
trace_id = str(uuid.uuid4())
trace_token = trace_id_ctx.set(trace_id)
root_span_id, root_started_at = start_span("gateway", "handle_request", parent_span_id=None)
span_token = span_id_ctx.set(root_span_id)
try:
headers = {}
inject_context(headers)
call_worker_service(headers) # умовний HTTP/gRPC виклик до worker_handle_request на іншому сервісі
finish_span(
"gateway",
"handle_request",
root_span_id,
root_started_at,
status="ok",
parent_span_id=None,
)
except Exception as error:
finish_span(
"gateway",
"handle_request",
root_span_id,
root_started_at,
status="error",
parent_span_id=None,
error=str(error),
)
raise
finally:
span_id_ctx.reset(span_token)
trace_id_ctx.reset(trace_token)
def worker_handle_request(headers):
parent_span_id, trace_token = extract_context(headers)
span_id, started_at = start_span("worker", "process_task", parent_span_id=parent_span_id)
span_token = span_id_ctx.set(span_id)
try:
# ... робота агента, tool calls, LLM кроки ...
finish_span("worker", "process_task", span_id, started_at, status="ok", parent_span_id=parent_span_id)
except Exception as error:
finish_span(
"worker",
"process_task",
span_id,
started_at,
status="error",
parent_span_id=parent_span_id,
error=str(error),
)
raise
finally:
span_id_ctx.reset(span_token)
trace_id_ctx.reset(trace_token)
Навіть ручний підхід вище допомагає зрозуміти базову механіку distributed tracing.
У реальному workflow кожен сервіс зазвичай створює свій span і далі прокидає вже його span_id як parent_span_id для наступного hop.
Якщо цей крок пропустити, наступний сервіс почне новий trace і end-to-end картина зламається.
Наприклад, один span-event у JSON-логу може виглядати так:
{
"timestamp": "2026-03-21T15:17:00Z",
"event": "span_finished",
"trace_id": "tr_7a31",
"span_id": "sp_worker_02",
"parent_span_id": "sp_gateway_01",
"service_name": "worker",
"operation_name": "process_task",
"latency_ms": 410,
"status": "ok"
}
Типові помилки
Навіть якщо distributed tracing уже додано, у production часто залишаються типові проблеми.
Новий trace_id у кожному сервісі
Якщо кожен сервіс генерує свій trace_id, end-to-end трейс розпадається на шматки.
У такому режимі складно локалізувати причину інциденту між сервісами.
Передається лише trace_id, без зв'язку спанів
trace_id без span_id і parent_span_id дає лише плоский список подій.
Без дерева спанів важко зрозуміти, які кроки були вкладеними.
Контекст губиться в async-чергах
Якщо metadata черги не містить trace context, частина workflow випадає з трейсу.
Такі обриви часто маскують ранню фазу часткового збою або каскадних збоїв.
Немає service_name і operation_name
Без цих полів видно, що помилка є, але незрозуміло, в якому саме сервісі й операції вона виникла. Через це дебаг займає значно більше часу.
Самоперевірка
Нижче — короткий checklist базового distributed tracing перед релізом.
Прогрес: 0/9
⚠ Бракує базової observability
Систему буде складно дебажити в production. Почніть з run_id, structured logs і tracing tool calls.
FAQ
Q: Чим distributed tracing відрізняється від звичайного agent tracing?
A: Agent tracing показує кроки в межах одного runtime. Distributed tracing з'єднує кроки між кількома сервісами в один end-to-end трейс.
Q: Що ламається, якщо між сервісами прокидається лише trace_id, без span_id і parent_span_id?
A: Тоді трейс стає плоским: видно, що події належать до одного шляху, але вже важко зрозуміти, які кроки були вкладеними, який сервіс кого викликав і де саме виникла затримка або помилка. trace_id з'єднує загальний шлях, а span_id і parent_span_id будують дерево кроків.
Q: Як передавати trace context через черги?
A: Додавай trace_id, span_id і parent_span_id у metadata повідомлення. Інакше async-кроки випадуть з трейсу.
Q: Чи обов'язково одразу впроваджувати OpenTelemetry?
A: Ні. Можна почати з ручної propagation і структурованих логів, а потім перейти на OTel SDK, коли система зросте.
Пов'язані сторінки
Далі за темою:
- Observability для AI-агентів — базова картина traces, logs і metrics.
- Трейсинг агента — трейсинг одного run у межах сервісу.
- Дебаг запусків агентів — як розбирати проблемні run крок за кроком.
- Алерти збоїв агентів — як вчасно ловити обриви й деградацію.
- Метрики агентів — які метрики потрібні для production-моніторингу.