← Назад к вопросам

Что выдаёт trace ID и где он формируется?

1.8 Middle🔥 111 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Trace ID: формирование и использование

Trace ID — это уникальный идентификатор для отслеживания запроса через всю распределённую систему. Это критичный компонент наблюдаемости (observability).

Что такое Trace ID

Trace ID — это уникальный идентификатор, который:

  • Создается при входе запроса в систему
  • Передается через все микросервисы и компоненты
  • Позволяет собрать полный путь запроса через систему
  • Помогает дебагать проблемы в распределённых системах

Архитектура распределённого трейсирования

Client Request
    |
    v
[API Gateway] ← создает Trace ID
    |
    v
[Auth Service] ← получает Trace ID, создает Span
    |
    v
[Order Service] ← получает Trace ID, создает Span
    |
    v
[Payment Service] ← получает Trace ID, создает Span
    |
    v
Trace ID собирается в Jaeger/Zipkin/Datadog

Где формируется Trace ID

Trace ID формируется в одном из этих мест:

  1. API Gateway (самое частое место)

    • Входная точка всех запросов
    • Генерирует уникальный ID для нового запроса
    • Передает его дальше
  2. Client side (если есть клиент)

    • Иногда клиент генерирует ID
    • Отправляет в header запроса
  3. Service side (если нет входной точки)

    • Сервис генерирует, если не получил
    • Это fallback вариант

Как генерируется Trace ID

Обычно это UUID4 или аналог:

import uuid
import secrets
import base64

# Способ 1: UUID4
trace_id = str(uuid.uuid4())
# Результат: "550e8400-e29b-41d4-a716-446655440000"

# Способ 2: Random hex (более компактный)
trace_id = secrets.token_hex(16)
# Результат: "a3f2b8c9d0e1f2a3b4c5d6e7f8a9b0c1"

# Способ 3: Jaeger формат (128 bit = 32 hex символа)
trace_id = "{:032x}".format(secrets.randbits(128))
# Результат: "a3f2b8c9d0e1f2a3b4c5d6e7f8a9b0c1"

Реализация в FastAPI

from fastapi import FastAPI, Request, Header
from typing import Optional
import uuid
import logging

app = FastAPI()

logger = logging.getLogger(__name__)

# Middleware для создания/передачи Trace ID
@app.middleware("http")
async def trace_id_middleware(request: Request, call_next):
    # Проверяем, есть ли уже Trace ID (от upstream сервиса)
    trace_id = request.headers.get(
        "X-Trace-ID",
        str(uuid.uuid4())
    )
    
    # Логируем с Trace ID
    request.state.trace_id = trace_id
    logger.info(
        f"Request started",
        extra={"trace_id": trace_id}
    )
    
    # Обработка запроса
    response = await call_next(request)
    
    # Возвращаем Trace ID в ответе
    response.headers["X-Trace-ID"] = trace_id
    
    return response

@app.get("/api/order")
async def get_order(request: Request):
    trace_id = request.state.trace_id
    logger.info(
        f"Processing order",
        extra={"trace_id": trace_id}
    )
    return {"order_id": 123, "trace_id": trace_id}

Передача Trace ID между сервисами

import httpx

async def call_downstream_service(
    trace_id: str,
    url: str
) -> dict:
    """Вызов другого сервиса с传递 Trace ID"""
    headers = {
        "X-Trace-ID": trace_id,  # Передаем Trace ID
        "X-Span-ID": str(uuid.uuid4())  # Создаем Span ID для этого вызова
    }
    
    async with httpx.AsyncClient() as client:
        response = await client.get(
            url,
            headers=headers
        )
        return response.json()

# Использование
trace_id = request.state.trace_id
result = await call_downstream_service(
    trace_id=trace_id,
    url="http://payment-service/api/pay"
)

Span ID vs Trace ID

Это разные концепции:

Trace ID: "550e8400-e29b-41d4-a716-446655440000"
    |
    +-- Span ID: "a3f2b8c9" (запрос к API Gateway)
    +-- Span ID: "c5d6e7f8" (запрос к Auth Service)
    +-- Span ID: "d0e1f2a3" (запрос к Order Service)
        +-- Span ID: "b4c5d6e7" (вложенный вызов БД)

Стандарты и спецификации

W3C Trace Context (современный стандарт):

# Header format
headers = {
    "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
    #             version   trace-id                    parent-id     flags
}

# Парсинг
from w3c_trace_context import TraceContext

tracecontext = TraceContext.from_header(
    "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
)
print(tracecontext.trace_id)  # 4bf92f3577b34da6a3ce929d0e0e4736
print(tracecontext.parent_id)  # 00f067aa0ba902b7

Инструменты для трейсирования

Jaeger — популярный choice для микросервисов:

from jaeger_client import Config
from opentelemetry import trace
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# Конфигурация Jaeger
jaeger_exporter = JaegerExporter(
    agent_host_name="localhost",
    agent_port=6831,
)

jaeger_provider = TracerProvider()
jaeger_provider.add_span_processor(
    BatchSpanProcessor(jaeger_exporter)
)
trace.set_tracer_provider(jaeger_provider)

# Получение tracer
tracer = trace.get_tracer(__name__)

# Создание span
with tracer.start_as_current_span("database_query") as span:
    span.set_attribute("trace_id", trace_id)
    result = db.query()

Logging с Trace ID

Импортант связать логи с Trace ID:

import logging
import json

class TraceIDFormatter(logging.Formatter):
    def format(self, record):
        # Добавляем Trace ID в каждое сообщение лога
        trace_id = getattr(record, "trace_id", "unknown")
        record.trace_id = trace_id
        return super().format(record)

# Конфигурация логирования
logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
formatter = TraceIDFormatter(
    '[%(trace_id)s] %(levelname)s: %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)

# Использование
logger.info(
    "Order processed",
    extra={"trace_id": "550e8400-e29b-41d4-a716-446655440000"}
)

Практический пример: полный flow

from fastapi import FastAPI, Request
from contextlib import asynccontextmanager
import uuid
import httpx

app = FastAPI()

# Context variable для хранения Trace ID
from contextvars import ContextVar
trace_id_var: ContextVar[str] = ContextVar('trace_id')

@app.middleware("http")
async def add_trace_id(request: Request, call_next):
    # Создаем или получаем Trace ID
    trace_id = request.headers.get(
        "X-Trace-ID",
        str(uuid.uuid4())
    )
    trace_id_var.set(trace_id)
    
    # Обработка
    response = await call_next(request)
    response.headers["X-Trace-ID"] = trace_id
    return response

@app.get("/api/order/{order_id}")
async def get_order(order_id: int):
    trace_id = trace_id_var.get()
    
    # Вызов Payment Service
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            f"http://payment-service/check/{order_id}",
            headers={"X-Trace-ID": trace_id}
        )
    
    return {
        "order_id": order_id,
        "trace_id": trace_id,
        "payment_status": resp.json()["status"]
    }

Best Practices

  1. Всегда передавай Trace ID — в headers всех http запросов
  2. Создавай в одном месте — обычно в API Gateway
  3. Логируй с Trace ID — связи все логи воедино
  4. Используй стандарты — W3C Trace Context
  5. Отправляй в хранилище — Jaeger, Datadog, Elasticsearch
  6. Анализируй patterns — найди проблемы в распределённых запросах
Что выдаёт trace ID и где он формируется? | PrepBro