Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Correlation ID: сквозной идентификатор запроса

Correlation ID: связка логов одного запроса

Сквозной идентификатор запроса: генерация, передача через сервисы, добавление в логи и поиск записей одного обращения.

Прямой ответ

Сквозной идентификатор запроса добавляется к каждой записи лога, поэтому все записи одного обращения находятся одним запросом независимо от числа сервисов.

Шаг Что выполняется
Генерация Идентификатор создаётся при входе запроса
Передача Идентификатор передаётся в заголовках вызовам
Добавление Идентификатор включается в каждую запись лога
Поиск Записи одного обращения выбираются по идентификатору
from fastapi import Request
import uuid

@app.middleware("http")
async def correlation(request: Request, call_next):
    cid = request.headers.get("X-Correlation-Id") or str(uuid.uuid4())
    request.state.cid = cid
    response = await call_next(request)
    response.headers["X-Correlation-Id"] = cid
    return response

Идентификатор, полученный из заголовка, сохраняется, поэтому идентификатор, созданный на границе, сохраняется по всему пути. О распространении контекста — в статье про контекст запроса.

Передача идентификатора между сервисами

Идентификатор передаётся в исходящих вызовах, поэтому цепочка сервисов сохраняет единый признак обращения.

Исходящий вызов. Клиент включает идентификатор в заголовок исходящего запроса, поэтому принимающий сервис продолжает тот же признак.

Сохранение входящего. Идентификатор, полученный в заголовке, сохраняется и используется дальше, поэтому новый признак не создаётся.

Создание при отсутствии. При отсутствии входящего идентификатора создаётся новый, поэтому обращение без признака не остаётся без него.

Практическое следствие: создание нового идентификатора при наличии входящего разрывает цепочку, поэтому часть пути не находится по исходному признаку.

Отдельный аспект — включение идентификатора в запись лога. Идентификатор добавляется к каждой записи, поэтому поиск по нему возвращает все записи обращения.

Три признака некорректной реализации.

Отсутствие передачи в исходящих вызовах. Идентификатор не доходит до следующего сервиса, поэтому цепочка разрывается.

Создание нового при наличии входящего. Цепочка разрывается, поэтому часть пути не находится.

Отсутствие идентификатора в записи лога. Поиск по признаку не возвращает записи обращения.

О структуре записей для поиска — в статье про структуру логов; о связи идентификатора с трейсом — в статье про основы трассировки.

Формат идентификатора и совместимость

Формат идентификатора определяет возможность сопоставления записей и перехода к дереву операций.

Единый формат. Идентификатор одинакового формата во всех сервисах обеспечивает сопоставление записей без приведения.

Совместимость с трейсом. Идентификатор трейса служит тем же признаком обращения, поэтому переход от записи к дереву выполняется по совпадающему значению.

Уникальность. Идентификатор уникален в пределах обращения, поэтому записи разных обращений не смешиваются.

Практическое следствие: разный формат идентификатора в сервисах требует приведения при сопоставлении, поэтому поиск усложняется.

Практика: настройка сквозного идентификатора

1. Генерируйте идентификатор при входе запроса. Признак создаётся на границе, поэтому все последующие записи относятся к нему.

2. Сохраняйте входящий идентификатор. Признак, полученный в заголовке, используется дальше, поэтому цепочка не разрывается.

3. Передавайте идентификатор в исходящих вызовах. Признак включается в заголовок, поэтому следующий сервис продолжает цепочку.

4. Добавляйте идентификатор к каждой записи лога. Поиск по признаку возвращает все записи обращения.

5. Возвращайте идентификатор в ответе. Пользователь получает признак, по которому обращение находится в логах.

6. Используйте единый формат идентификатора. Единый формат упрощает поиск и сопоставление записей.

7. Проверяйте полноту цепочки. Отсутствие идентификатора в части записей указывает на разрыв передачи.

Рекомендации:

  1. Не создавайте новый идентификатор при наличии входящего. Новый признак разрывает цепочку, поэтому часть пути не находится.
  2. Учитывайте передачу через промежуточные узлы. Прокси может не передавать заголовок, поэтому признак теряется.
  3. Сопоставляйте идентификатор с трейсом. Связь признака обращения с идентификатором трейса позволяет перейти от записи к дереву операций.
  4. Фиксируйте идентификатор в ошибке. Признак в записи ошибки позволяет найти остальные записи обращения.
  5. Пересматриваем передачу при добавлении сервисов. Новый сервис требует включения признака в заголовки вызовов.

Практический сценарий: записи обращения не собрались

Контекст: часть записей обращения находилась по признаку, а часть отсутствовала, поэтому полная картина не складывалась.

  1. Диагностика. Проверка исходящих вызовов показала, что идентификатор не включался в заголовок при обращении к одному из сервисов. Записи этого сервиса создавались без признака, поэтому поиск по идентификатору их не возвращал.
  2. Причина. Передача идентификатора выполнялась не во всех исходящих вызовах, поэтому часть пути оставалась без признака.
  3. Действия. (а) Добавили передачу идентификатора в заголовки всех исходящих вызовов; (б) проверили, что принимающий сервис сохраняет входящий признак, а не создаёт новый; (в) включили идентификатор в формат записи лога каждого сервиса; (г) проверили поиск по признаку и убедились, что записи всех сервисов возвращаются; (д) добавили возврат идентификатора в ответе, чтобы признак был доступен пользователю.
  4. Результат. Все записи обращения находятся одним запросом по идентификатору.

Вывод: поиск по идентификатору возвращает записи только тех сервисов, которые получили и сохранили признак. Полнота цепочки обеспечивается передачей идентификатора во всех исходящих вызовах и включением признака в формат записи каждого сервиса.

Практический сценарий: записи обращения не собрались

Контекст: часть записей обращения находилась по признаку, а часть отсутствовала, поэтому полная картина не складывалась.

  1. Диагностика. Проверка исходящих вызовов показала, что идентификатор не включался в заголовок при обращении к одному из сервисов. Записи этого сервиса создавались без признака, поэтому поиск по идентификатору их не возвращал.
  2. Причина. Передача идентификатора выполнялась не во всех исходящих вызовах, поэтому часть пути оставалась без признака.
  3. Действия. (а) Добавили передачу идентификатора в заголовки всех исходящих вызовов; (б) проверили, что принимающий сервис сохраняет входящий признак, а не создаёт новый; (в) включили идентификатор в формат записи лога каждого сервиса; (г) проверили поиск по признаку и убедились, что записи всех сервисов возвращаются; (д) добавили возврат идентификатора в ответе, чтобы признак был доступен пользователю.
  4. Результат. Все записи обращения находятся одним запросом по идентификатору.

Вывод: поиск по идентификатору возвращает записи только тех сервисов, которые получили и сохранили признак. Полнота цепочки обеспечивается передачей идентификатора во всех исходящих вызовах и включением признака в формат записи каждого сервиса.

FAQ

Зачем нужен сквозной идентификатор? Он связывает записи одного обращения в разных сервисах, поэтому поиск по признаку возвращает все записи независимо от числа сервисов.

Что делать при наличии входящего идентификатора? Сохранять и использовать дальше, поскольку создание нового разрывает цепочку. Новый признак создаётся только при отсутствии входящего.

Как идентификатор передаётся между сервисами? Через заголовок исходящего запроса. Принимающий сервис сохраняет признак и использует дальше.

Как идентификатор связан с трейсом? Идентификатор трейса служит тем же признаком обращения, поэтому связь признака с трейсом позволяет перейти от записи лога к дереву операций.

Источники: OpenTelemetry traces, FastAPI middleware, Python logging — ссылки в шапке.