ПроКодинг - Откроем для вас мир IT!

Вы когда-нибудь сталкивались с ситуацией, когда в продакшене сломался запрос, но логи Python-бэкенда и TypeScript-фронтенда не сопоставляются? Вы тратите часы, переключаясь между окнами, ищете один и тот же ID, чтобы понять, где именно сломалось? Это не проблема вашего кода - это проблема логов. Когда бэкенд и фронтенд пишут логи в разных форматах, с разными метками, без единого идентификатора, вы не можете отследить путь запроса от клиента до базы данных. Это как пытаться найти ключ в темной комнате, где каждый предмет лежит в другом углу.

Почему согласованное логирование - это не опция, а необходимость

Современные приложения - это не монолиты. Это микросервисы, фронтенд на React, бэкенд на FastAPI, база данных PostgreSQL, кэш Redis и ещё несколько компонентов. Запрос проходит через 5-7 точек, прежде чем ответ вернётся пользователю. Если в каждой точке логи пишутся по-своему, вы теряете целостность.

Например, пользователь сообщает, что кнопка «Оплатить» не работает. Вы смотрите в логи фронтенда - там есть POST /api/payment с кодом 500. Вы переходите к бэкенду - там десятки логов за последние 30 секунд. Где именно упал запрос? Какой из них соответствует этому пользователю? Без единого идентификатора - вы не узнаете. И это не теория. Это реальная ситуация, с которой мы сталкивались в проекте для онлайн-магазина в Казани. Время на диагностику упало с 4 часов до 12 минут, как только мы внедрили согласованное логирование.

Как работает E2E трассировка

E2E - это End-to-End. То есть от начала до конца. Трассировка - это не просто запись логов. Это передача единого идентификатора запроса через всю цепочку. Этот идентификатор называется trace_id. Он должен быть:

  • Уникальным для каждого запроса
  • Генерируемым на клиенте (фронтенд)
  • Передаваемым в HTTP-заголовках
  • Логируемым в каждом компоненте
  • Читаемым в логах как ключевое поле

Вот как это выглядит на практике:

  1. Пользователь кликает «Оплатить» - TypeScript-приложение генерирует trace_id = a1b2c3d4 и добавляет его в заголовок X-Trace-ID.
  2. Запрос уходит на Python-сервер. FastAPI получает заголовок и сохраняет trace_id в контексте запроса.
  3. Python-код логирует: [trace_id=a1b2c3d4] Processing payment for user 789.
  4. Python обращается к PostgreSQL - лог запроса к БД тоже содержит trace_id=a1b2c3d4.
  5. Ответ возвращается на фронтенд - TypeScript логирует: [trace_id=a1b2c3d4] Received 500 from API.

Теперь, когда вы ищете проблему, вы вводите a1b2c3d4 в любой системе логов - и видите всю цепочку. От клиента до базы данных. Без переключений. Без догадок.

Как реализовать это в TypeScript

В TypeScript-приложении (React, Vue, Svelte) вы используете interceptors - это обработчики HTTP-запросов. Вот как это делается в Axios:

import axios from 'axios';

const apiClient = axios.create();

apiClient.interceptors.request.use((config) => {
  const traceId = generateTraceId(); // генерируем уникальный ID
  config.headers['X-Trace-ID'] = traceId;
  return config;
});

function generateTraceId() {
  return Math.random().toString(36).substring(2, 15) + 
         Math.random().toString(36).substring(2, 15);
}

Теперь каждый запрос из фронтенда содержит X-Trace-ID. Но это только начало. Логи на фронтенде тоже должны его записывать. Для этого используйте обёртку над console.log:

const log = (message, context = {}) => {
  const traceId = localStorage.getItem('traceId') || generateTraceId();
  localStorage.setItem('traceId', traceId);
  console.log(`[trace_id=${traceId}] ${message}`, context);
};

// Используете так:
log('User clicked pay button', { userId: 789 });

Это не идеально - логи в браузере не сохраняются навсегда, но для диагностики в реальном времени - достаточно. Для продакшена лучше использовать Sentry или аналоги, которые поддерживают трассировку.

Светящаяся цепочка соединяет браузер, сервер и базу данных через единый trace_id, символизируя полную трассировку запроса.

Как реализовать это в Python

В Python (FastAPI, Flask) всё проще. Вы создаёте middleware, который извлекает X-Trace-ID из заголовка и сохраняет его в контексте. Вот пример для FastAPI:

from fastapi import FastAPI, Request
import uuid
import logging

app = FastAPI()

# Настраиваем логгер
logging.basicConfig(
    level=logging.INFO,
    format='[trace_id=%(trace_id)s] %(message)s'
)

@app.middleware("http")
async def add_trace_id(request: Request, call_next):
    trace_id = request.headers.get("X-Trace-ID") or str(uuid.uuid4())
    
    # Создаём логгер с контекстом
    logger = logging.LoggerAdapter(
        logging.getLogger("app"), 
        {"trace_id": trace_id}
    )
    
    # Сохраняем trace_id в контекст для дальнейшего использования
    request.state.trace_id = trace_id
    
    response = await call_next(request)
    
    # Логируем ответ
    logger.info(f"Request completed with status {response.status_code}")
    
    return response

Теперь каждый лог в Python автоматически содержит trace_id. Вы можете использовать его в любом месте кода:

@app.post("/api/payment")
async def process_payment(request: Request):
    trace_id = request.state.trace_id
    logger.info("Starting payment processing")
    
    # Обращаемся к БД
    db_result = await db.execute("SELECT balance FROM users WHERE id = %s", [user_id])
    logger.info("Database query executed", extra={"trace_id": trace_id})
    
    # Вызываем внешний сервис
    payment_response = await external_service.charge(user_id)
    logger.info("External payment service called", extra={"trace_id": trace_id})
    
    return {"status": "success"}

Обратите внимание: мы явно передаём trace_id в extra. Это важно - потому что логгер может быть вызван не через middleware, а в другом модуле. Без этого - потеря контекста.

Как объединить логи в одном месте

Теперь у вас есть логи в TypeScript и Python - с одинаковым trace_id. Но они лежат в разных местах: фронтенд - в браузере, бэкенд - на сервере. Чтобы увидеть всю цепочку, нужно собрать их в одном месте.

Для этого используйте лог-агрегатор. Лучшие варианты - ELK Stack (Elasticsearch + Logstash + Kibana) или Grafana Loki. Они позволяют:

  • Собирать логи из разных источников
  • Индексировать их по полям (включая trace_id)
  • Искать по одному ID и видеть все события

Например, в Kibana вы вводите trace_id:a1b2c3d4 - и видите:

  • [trace_id=a1b2c3d4] User clicked pay button - из фронтенда
  • [trace_id=a1b2c3d4] Request received: POST /api/payment - из FastAPI
  • [trace_id=a1b2c3d4] Database query executed - из Python
  • [trace_id=a1b2c3d4] Payment failed: insufficient funds - из внешнего сервиса

Всё это - в одной строке. Без переключения между окнами. Без копипаста ID. Это не фича - это стандарт.

Что ещё нужно учесть

Есть нюансы, которые ломают трассировку, если их игнорировать:

  • Не используйте console.log в продакшене. Он не сохраняется. Используйте Sentry, LogRocket или аналоги.
  • Не генерируйте trace_id только на сервере. Если фронтенд не знает ID, вы теряете начало цепочки.
  • Не используйте request_id вместо trace_id. Это разные вещи. trace_id - это путь запроса, request_id - это ID запроса в одном сервисе.
  • Не забывайте про прокси. Если запрос проходит через Nginx, Cloudflare или API Gateway - убедитесь, что они передают X-Trace-ID.
  • Сохраняйте trace_id в ответе. Добавьте заголовок X-Trace-ID в ответ от бэкенда. Это поможет клиенту связать запрос с ответом, если что-то пошло не так.
Три панели: пользователь кликает кнопку, сервер получает trace_id, база данных логирует его — всё связано единым идентификатором.

Как проверить, что всё работает

Проверить трассировку просто:

  1. Откройте DevTools в браузере.
  2. Зайдите на страницу с оплатой.
  3. Нажмите «Оплатить».
  4. В Network-вкладке найдите запрос /api/payment.
  5. Проверьте заголовки - там должен быть X-Trace-ID.
  6. Скопируйте этот ID.
  7. Зайдите в Kibana или Loki.
  8. Вставьте ID в поиск.
  9. Увидите все логи - от фронтенда до БД.

Если вы видите всё - значит, всё работает. Если нет - ищите, где потерялся ID. Чаще всего - в прокси или в необработанном middleware.

Что дальше? Автоматизация и алерты

Когда трассировка работает, вы можете идти дальше:

  • Настроить алерты: «Если trace_id содержит ошибку 500 в двух сервисах подряд - отправить уведомление».
  • Создать дашборд: «Сколько запросов прошло без ошибок за час?»
  • Использовать OpenTelemetry - это стандарт, который автоматически генерирует trace_id и передаёт его через все компоненты.

OpenTelemetry - это не просто инструмент. Это философия: все логи должны быть связаны. Он поддерживает и Python, и TypeScript, и даже базы данных. Но начинать можно и с простого - X-Trace-ID и логгеры. Это уже даст 90% эффекта.

Итог: трассировка - это не про логи, а про доверие

Когда вы знаете, что каждый запрос можно отследить от начала до конца - вы перестаёте бояться продакшена. Вы перестаёте тратить часы на поиск ошибок. Вы начинаете доверять системе. Это не про техническую красоту. Это про то, чтобы не терять время, когда пользователь ждёт.

Ваша система - это цепочка. Если одно звено сломано - всё падает. Логи - это ваши глаза. Сделайте их едиными. И вы увидите всё.

Что делать, если trace_id не передаётся в заголовках?

Проверьте, не блокируются ли кастомные заголовки прокси (Nginx, Cloudflare, API Gateway). В Nginx добавьте: proxy_pass_header X-Trace-ID;. В Cloudflare - включите передачу кастомных заголовков в настройках Workers или Page Rules. Также убедитесь, что фронтенд не удаляет заголовки перед отправкой - например, через CORS-политику.

Можно ли использовать UUIDv4 для trace_id?

Да, UUIDv4 - отличный вариант. Он гарантированно уникален и легко генерируется в Python (uuid.uuid4()) и TypeScript (crypto.randomUUID()). Не используйте случайные строки вроде Math.random() - они могут повторяться. UUIDv4 имеет 122 бита случайности - вероятность коллизии ничтожна.

Как логировать в базе данных с trace_id?

В PostgreSQL можно использовать SET LOCAL для сессионных переменных. В Python - передавайте trace_id в SQL-запрос как параметр и логируйте его в таблице audit-логов. Например: INSERT INTO audit_log (trace_id, action, timestamp) VALUES (%s, %s, NOW()). Это позволяет связывать действия в БД с конкретными запросами.

Нужно ли использовать OpenTelemetry сразу?

Нет. OpenTelemetry - мощный инструмент, но он требует настройки агентов, экспортеров и коллекторов. Начните с простого: X-Trace-ID + логи с одинаковым форматом. Когда система стабильна - переходите на OpenTelemetry. Это снизит риски и позволит понять, как работает трассировка, прежде чем добавлять сложность.

Какие инструменты лучше всего подходят для анализа логов?

Для небольших проектов - Grafana Loki + Promtail. Для средних - ELK Stack (Elasticsearch + Logstash + Kibana). Для больших - Datadog, New Relic или AWS CloudWatch. Все они поддерживают поиск по trace_id. Главное - чтобы логи приходили в формате JSON с полем trace_id. Это делает поиск мгновенным.