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

Как используется trace ID при отладке и мониторинге?

2.8 Senior🔥 131 комментариев
#DevOps и инфраструктура#Архитектура и паттерны

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

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

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

Использование Trace ID при отладке и мониторинге

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

1. Основная концепция Trace ID

Trace ID позволяет отследить полный путь одного запроса через все компоненты системы:

Клиент
  ↓ (Request с trace_id: abc123)
API Gateway
  ↓ (abc123 передаётся дальше)
User Service
  ↓ (abc123)
Database
  ↓
Cache Service
  ↓
Audit Service
  ↓ (все логируют abc123)
Клиент ← Response с trace_id: abc123

2. Реализация Trace ID в FastAPI/Django

FastAPI пример:

import uuid
from fastapi import FastAPI, Request
from fastapi.middleware.base import BaseHTTPMiddleware
import logging

app = FastAPI()

logger = logging.getLogger(__name__)

class TraceIDMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        # Получаем trace_id из заголовка или генерируем новый
        trace_id = request.headers.get('X-Trace-ID', str(uuid.uuid4()))
        
        # Сохраняем в контекст запроса
        request.state.trace_id = trace_id
        
        # Добавляем в логи
        response = await call_next(request)
        response.headers['X-Trace-ID'] = trace_id
        
        return response

app.add_middleware(TraceIDMiddleware)

@app.get('/users/{user_id}')
async def get_user(user_id: int, request: Request):
    trace_id = request.state.trace_id
    logger.info(f'[{trace_id}] Получение пользователя {user_id}')
    return {'user_id': user_id, 'trace_id': trace_id}

3. Использование Trace ID в сервисах

Передаём trace_id между вызовами микросервисов:

import httpx
from contextvars import ContextVar

trace_id_var: ContextVar[str] = ContextVar('trace_id', default=None)

class ServiceClient:
    def __init__(self):
        self.client = httpx.AsyncClient()
    
    async def call_service(self, service_url: str, endpoint: str):
        """Вызываем другой сервис с передачей trace_id."""
        trace_id = trace_id_var.get()
        
        headers = {}
        if trace_id:
            headers['X-Trace-ID'] = trace_id
        
        response = await self.client.get(
            f'{service_url}{endpoint}',
            headers=headers
        )
        return response.json()

# Использование
service_client = ServiceClient()

@app.get('/orders/{order_id}')
async def get_order(order_id: int, request: Request):
    trace_id = request.state.trace_id
    trace_id_var.set(trace_id)
    
    logger.info(f'[{trace_id}] Получение заказа {order_id}')
    
    # Вызываем другой сервис
    user_data = await service_client.call_service(
        'http://user-service:8000',
        f'/users/{order_id}'
    )
    
    return {'order_id': order_id, 'user_data': user_data}

4. Логирование с Trace ID

Добавляем trace_id во все логи автоматически:

import logging
from contextvars import ContextVar

trace_id_var: ContextVar[str] = ContextVar('trace_id', default='unknown')

class TraceIDFilter(logging.Filter):
    """Filter для добавления trace_id в логи."""
    def filter(self, record):
        record.trace_id = trace_id_var.get()
        return True

# Настройка логирования
logging.basicConfig(
    format='%(asctime)s - [%(trace_id)s] - %(name)s - %(levelname)s - %(message)s'
)

logger = logging.getLogger(__name__)
logger.addFilter(TraceIDFilter())

# Использование
trace_id_var.set('req-12345')
logger.info('Обрабатываем запрос')  # [req-12345] Обрабатываем запрос
logger.error('Ошибка обработки')     # [req-12345] Ошибка обработки

5. Trace ID в асинхронном коде

Используем contextvars для асинхронных операций:

import asyncio
from contextvars import ContextVar

trace_id_var: ContextVar[str] = ContextVar('trace_id', default=None)

async def database_query(query: str):
    """Асинхронный запрос с trace_id."""
    trace_id = trace_id_var.get()
    logger.info(f'[{trace_id}] Выполнение запроса: {query}')
    await asyncio.sleep(0.1)  # Имитация запроса
    return {'result': 'success'}

async def process_request(trace_id: str):
    token = trace_id_var.set(trace_id)
    try:
        result1 = await database_query('SELECT * FROM users')
        result2 = await database_query('SELECT * FROM orders')
        return {'data': [result1, result2]}
    finally:
        trace_id_var.reset(token)

# Использование
await process_request('trace-abc123')

6. Интеграция с системой мониторинга

Передаём trace_id в OpenTelemetry или другую систему:

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,
)

trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(
    BatchSpanProcessor(jaeger_exporter)
)

tracer = trace.get_tracer(__name__)

@app.get('/users/{user_id}')
async def get_user(user_id: int, request: Request):
    trace_id = request.state.trace_id
    
    with tracer.start_as_current_span('get_user') as span:
        span.set_attribute('trace_id', trace_id)
        span.set_attribute('user_id', user_id)
        
        logger.info(f'[{trace_id}] Получение пользователя {user_id}')
        user = await fetch_user(user_id)
        
        return {'user': user, 'trace_id': trace_id}

7. Структурное логирование с Trace ID

Используем структурированные логи с bibliotekами типа structlog:

import structlog
from contextvars import ContextVar

trace_id_var: ContextVar[str] = ContextVar('trace_id', default=None)

structlog.configure(
    processors=[
        structlog.stdlib.filter_by_level,
        structlog.stdlib.add_logger_name,
        structlog.stdlib.add_log_level,
        structlog.stdlib.PositionalArgumentsFormatter(),
        structlog.processors.TimeStamper(fmt='iso'),
        structlog.processors.StackInfoRenderer(),
        structlog.processors.format_exc_info,
        structlog.processors.UnicodeDecoder(),
        structlog.processors.JSONRenderer()
    ],
    context_class=dict,
    logger_factory=structlog.stdlib.LoggerFactory(),
    cache_logger_on_first_use=True,
)

logger = structlog.get_logger()

@app.get('/orders/{order_id}')
async def get_order(order_id: int, request: Request):
    trace_id = request.state.trace_id
    
    # Логируем с контекстом
    logger.info(
        'order_retrieved',
        trace_id=trace_id,
        order_id=order_id,
        timestamp=datetime.now().isoformat()
    )
    
    return {'order_id': order_id, 'trace_id': trace_id}

8. Анализ логов по Trace ID

Потом в системе мониторинга (ELK, CloudWatch, Datadog) ищем:

# Elasticsearch
GET /logs/_search
{
  "query": {
    "match": {
      "trace_id": "req-12345"
    }
  }
}

# CloudWatch Insights
fields @timestamp, @message, trace_id
| filter trace_id = "req-12345"
| stats count() as event_count

9. Лучшие практики

  • Всегда генерируйте trace_id для входящих запросов
  • Передавайте trace_id между всеми сервисами
  • Включайте trace_id в ВСЕ логи автоматически
  • Возвращайте trace_id в ответе клиенту
  • Используйте contextvars для отслеживания в асинхронном коде
  • Интегрируйте с системой мониторинга (Jaeger, DataDog, CloudWatch)
  • Сохраняйте логи достаточно долго для анализа

Trace ID — это необходимый инструмент для отладки распределённых систем и понимания поведения приложения в продакшене.

Как используется trace ID при отладке и мониторинге? | PrepBro