# Підтримка трасування запитів (OpenTelemetry-підхід)

## 1. Загальна схема взаємодії

Інтеграція працює за наступною схемою:

1. Сервіси Expirenza надсилають інтегратору команду через **WebSocket**.
2. Інтеграція виконує необхідні дії (свій бізнес-процес, звернення до внутрішніх сервісів тощо).
3. Інтеграція повертає результат у Expirenza через **HTTP callback**.
4. Увесь шлях запиту має бути пов’язаний одним і тим самим `traceId`.

Логічний ланцюжок виглядає так:

> Expirenza (internal service)\
> → Expirenza External Connector\
> → API інтегратора\
> → Expirenza External Connector\
> → Expirenza (internal service)

На кожному новому сервісі створюється **новий `spanId`**, при цьому:

* `traceId` залишається незмінним протягом усього життєвого циклу запиту;
* `parent-span-id` вказує, з якого span прийшов запит.

***

## 2. Структура повідомлень у WebSocket

Усі WebSocket-повідомлення від Expirenza до інтегратора мають службове поле `__headers__`.

Приклад вхідного повідомлення:

```json
{
    "rID" : "2021-03-13 00:32:04.942046#UP57RxE",
    "operation" : "categoriesInfo",
    "__headers__": {
    "traceId": "c0a8017f6f2c4d98bd70a35c90f1f01",
    "spanId": "7b9a3d3c4e2f1a90"
    }
}
```

#### 2.1. Поля в `__headers__`

* `traceId`
  * Унікальний ідентифікатор всього запиту (end-to-end).
  * Є **обов’язковим** до збереження та передачі назад у відповідь (HTTP callback).
  * Інтеграція не повинна змінювати формат чи значення `traceId`. Сприймайте його як “чорну скриньку”.
* `spanId`
  * Ідентифікатор поточного етапу (span) усередині загального запиту.
  * Для інтеграції він виконує роль **`parent-span-id`** – тобто span, від якого “відгалужується” робота інтеграції.
  * Обробка `spanId` є **рекомендованою, але не обов’язковою** (див. розділ 4).

***

## 3. Обов’язкові вимоги до інтеграції

#### 3.1. Збереження `traceId` в контексті

Після отримання WebSocket-повідомлення інтеграція повинна:

1. Прочитати `traceId` з `message.__headers__.['traceId']`.
2. Зберегти його в контексті обробки запиту (thread context, request-scoped context, MDC/лог-контекст тощо).
3. Використовувати цей `traceId` при всіх логуваннях, що стосуються даного запиту.

#### 3.2. Передача `traceId` в HTTP callback

При формуванні HTTP-відповіді (callback) інтеграція **обов’язково** має додати заголовок:

```
traceId: <значення traceId з WebSocket-повідомлення>
```

**Приклад HTTP callback:**

```json5
POST /expirenza/callback/payment HTTP/1.1
Host: api.expirenza.example
Content-Type: application/json
traceId: c0a8017f-6f2c-4d98-bd70-3a35c90f1f01

{
  "status": "SUCCESS",
  "orderId": "ORD-12345",
  "transactionId": "TX-98765"
}
```

Це дозволяє Expirenza “зшити” логи WebSocket-команди, внутрішніх сервісів та відповіді інтеграції в один наскрізний трейс.

{% hint style="warning" %}
Важливо: **передача `traceId` у callback – обов’язкова вимога**.\
Якщо `traceId` буде відсутній або змінений, трасування буде порушене.
{% endhint %}

***

## 4. Рекомендована (опційна) підтримка `spanId` та `parent-span-id`

Підтримка `spanId`/`parent-span-id` на стороні інтеграції **рекомендована**, але **не є обов’язковою**.\
Вона потрібна для більш детального трасування всередині системи інтеграції та між сервісами.

#### 4.1. Що рекомендується робити

1. Прочитати вхідний:
   * `traceId`
   * `spanId` (як **`parent-span-id`** для інтеграції)
2. Створити \*новий \* **`spanId`** для роботи інтеграції (наприклад, `spanId` інтеграції):
   * `parent-span-id` = вхідний `spanId` з WebSocket.
   * `spanId` = нове згенероване значення.
3. Якщо інтеграція викликає свої внутрішні сервіси:
   * передавати:
     * той самий `traceId`,
     * `spanId` (власний) (внутрішній сервіс запише його як `parent-span-id`)
     * Внутрішній сервіс сгенерує новий `spanId` для кожного дочірнього виклику.
4. При поверненні відповіді в Expirenza (HTTP callback) можна (опційно) передавати:

```jsonl
traceId: <обов'язково>
spanId: <span-id інтегратора з якого було здійснено http виклик>
```

Ще раз: **для Expirenza обов’язковим є тільки `traceId`**.\
Передача/ланцюжок `spanId` та `parent-span-id` – опція, яка дозволить глибше аналізувати ланцюжок викликів, якщо інтегратор цього бажає.&#x20;

***

## 5. Навіщо це потрібно інтеграції

Підтримка `traceId` (мінімально) та `spanId` (опційно) дає інтеграції такі переваги:

1. **Швидкий пошук інцидентів**
   * Можна отримати `traceId` від Expirenza (наприклад, з технічного запиту) і швидко знайти всі пов’язані логи та виклики у своїй системі.
2. **Кореляція логів між системами**
   * Expirenza та інтеграції дивляться на один і той самий `traceId`, що спрощує спільну діагностику.
3. **Глибинний аналіз продуктивності** (якщо використовуєте `spanId`)
   * Можна будувати детальні діаграми: скільки часу зайняла обробка в інтеграції, скільки – у внутрішніх сервісах інтеграції, де “вузьке місце”.
4. **Сумісність із OpenTelemetry та APM-системами**
   * Якщо інтеграція вже використовує OpenTelemetry, `traceId`, `spanId` та `parent-span-id` можна напряму мапити на відповідні сутності (trace/span/parent span).

***

## 6. Діаграма проходження запиту (послідовність)

{% @mermaid/diagram content="sequenceDiagram
participant IS as Expirenza Internal Service
participant EC as Expirenza External Connector
participant INT as Integrator API

```
Note over IS,INT: traceId = T1 (незмінний протягом усього життєвого циклу)

IS->>EC: Запит на зовнішній виклик (traceId=T1, spanId=S1)
EC->>INT: WebSocket повідомлення (traceId=T1, spanId=S2)

Note over INT: Інтегратор зберігає traceId=T1,<br/>spanId=S2 як parent-span-id,<br/>генерує власний spanId S3 (опційно)

INT->>INT: Внутрішня обробка (traceId=T1, spanId=S3, parent-span-id=S2)
INT-->>EC: HTTP callback (traceId=T1, spanId=S3)
EC-->>IS: Результат обробки (traceId=T1, spanId=S4, parent-span-id=S3)" %}
```

{% hint style="info" %}
Якщо інтеграція не реалізує `spanId`/`parent-span-id`, ви все одно повинні зберігати і повертати **`traceId`** – тоді всі вузли будуть пов’язані хоча б на рівні трейсів.
{% endhint %}

***

## 7. Підсумок вимог

**Обов’язково для інтеграції:**

1. Прочитати `traceId` із WebSocket-повідомлення (`__headers__.traceId`).
2. Зберігати `traceId` в контексті обробки запиту.
3. Логувати `traceId` для всіх дій у межах одного запиту.
4. Передавати `traceId` у HTTP callback як заголовок `traceId`.

**Опційно (рекомендовано):**

1. Використовувати `spanId` із WebSocket-повідомлення як `parent-span-id`.
2. Генерувати власні `spanId` для операцій інтеграції.
3. Передавати `spanId` у HTTP callback.
4. Інтегрувати цю схему з OpenTelemetry / APM системами для розширеної візуалізації трейсів.
