# Кореляція трейсів і логів без програмування за допомогою OBI

Site [llms.txt](/uk/llms.txt)

---

Вас викликають за тривогою. Трейс показує, що запит зазнав помилки в одному з ваших сервісів, і ви знаєте, що відповідь у логах — але які саме рядки логів належать _тому_ запиту? Якщо сервіс ніколи не впроваджував структуроване логування з контекстом трасування, чесна відповідь така: ви шукаєте за міткою часу й сподіваєтеся.

[OpenTelemetry eBPF Instrumentation (OBI)](https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation) тепер може додавати цей відсутній контекст трасування до логів, які ваші сервіси вже пишуть. Ваші застосунки не змінюються: без SDK, без налаштування бібліотеки логування, без повторної збірки чи повторного розгортання застосунку.

Одразу про межі застосування: це працює для логів, записаних у stdout або stderr — потоки, які перехоплює середовище виконання вашого контейнера, — і рядок анотується, коли OBI має активний контекст трасування для запиту, який обслуговується в момент запису. Усе, що ви розгортаєте, — це зміна конфігурації OBI та фільтр з одного рядка у вашому конвеєрі логів.

Під капотом OBI вже знає, через eBPF, який запит обслуговує кожен потік у момент, коли він пише рядок логу; у цьому й полягає вся хитрість. Поля кореляції додаються до того, як конвеєр логування контейнера отримує рядок. Далі в цій статті розповідається про те, що змінюється на практиці, що функція вимагає від вашого середовища та як її увімкнути.

## Що змінюється під час інциденту {#what-changes-during-an-incident}

Ваш застосунок пише таке:

```json
{ "level": "INFO", "message": "payment authorized", "amount": 42 }
```

Лог контейнера виглядає так:

```json
{
  "level": "INFO",
  "message": "payment authorized",
  "amount": 42,
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7"
}
```

Це ті самі ідентифікатори, які OBI зазначає для відрізків того запиту, тож кореляція працює в обидва напрямки: вставте `trace_id` із невдалого трейсу в пошук логів і отримайте саме ті рядки логів для цього запиту, або скопіюйте `trace_id` із підозрілого рядка логу у свій бекенд трейсів і потрапте на трейс, до якого він належить.

Це працює для логів у форматі JSON, NDJSON і простого тексту — до вільних рядків додається анотація `key=value`:

```text
payment authorized trace_id=4bf92f3577b34da6a3ce929d0e0e4736 span_id=00f067aa0ba902b7
```

Якщо ваш логер уже виводить одне з налаштованих полів, OBI зберігає його і додає лише те, чого бракує.

Ось як це виглядає наскрізно на невеликій демонстрації: Go-застосунок `frontend` без інструменталізації, який викликає Go-застосунок без інструменталізації `backend`, кожен логує один рядок JSON на запит, плюс OBI та Jaeger — загалом чотири контейнери. У коді застосунку немає жодного OpenTelemetry SDK.

Один запит до frontend створює один розподілений трейс у Jaeger — OBI також поширює контекст трасування між двома сервісами, тож відрізки frontend і backend обʼєднуються під одним трейсом:

![Jaeger показує відрізки frontend і backend одного трейсу](jaeger-trace.png)

Обидва сервіси записували звичайний JSON без полів трасування; OBI вставив відповідний контекст — той самий `trace_id` в обох сервісах, кожен зі своїм `span_id`:

![Збагачені логи з обох сервісів, що несуть той самий ідентифікатор трейсу](logs-and-trace.png)

Пошук у Jaeger за `trace_id` з будь-якого з цих рядків логів приводить саме до трейсу, показаного вище.

## Чи підходить це для вашого середовища? {#is-this-a-fit-for-your-environment}

Перевірте це перед тим, як планувати розгортання:

- **Місце призначення логів.** Збагачення охоплює логи, записані в stdout або stderr і перехоплені середовищем виконання контейнера. Логи, записані безпосередньо у файли або надіслані мережею дозатором, що працює в межах процесу, не охоплюються.
- **Активний контекст трасування.** Рядок збагачується лише тоді, коли він записується, поки OBI трасує запит у цьому сервісі — HTTP- чи gRPC-запит, клієнтський виклик або операцію з базою даних, що триває. Повідомлення про запуск і логи фонових завдань проходять без змін.
- **Ядро та привілеї.** Засобу збагачення логів OBI потрібен `CAP_SYS_ADMIN` і ядро, яке не перебуває в режимі блокування безпеки. Для збагачення звичайного шляху `write()` потрібне ядро Linux 6.0 або новіше; на старіших ядрах збагачуються лише записи на основі `writev()`, тож покриття залежить від того, як пише логер вашого середовища виконання.
- **Синхронне логування.** Звʼязок між рядком логу та запитом ґрунтується на тому, що запис відбувається з потоку, який обслуговує запит. Логери Go, Java та Ruby роблять це стандартно. У Node.js stdout асинхронний, коли його підтримує pipe — стандартно в контейнерах — тож за зворотного тиску на запис окремі рядки можуть зникати або нести застарілий контекст. Python потребує `PYTHONUNBUFFERED=1`; .NET потребує синхронного записувача консолі. [Віртуальні потоки Java поки що не збагачуються](https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/issues/2284); робочі навантаження на платформних потоках це не зачіпає.
- **Сервіси, інструментовані за допомогою SDK OTel.** Збагачення працює й там і є корисним, коли SDK експортує трейси, але не логи: OBI вставляє лише `trace_id`, бо згенеровані OBI ідентифікатори відрізків не збігалися б з ідентифікаторами SDK — а хибне посилання на відрізок гірше за його відсутність. Ви все одно можете знайти транзакцію в логах за ідентифікатором трейсу.

## Як увімкнути {#enable-it}

Засіб збагачення потрібно вмикати явно. У конфігурації версії 2 увімкніть його в `extensions.obi.correlation.log_trace_annotation`. Його список `match` приймає ті самі умови збігу, що й правила `capture`, і визначає, які захоплені робочі навантаження анотуються. Він має вибрати щонайменше одне робоче навантаження, а робоче навантаження поза вашим вибором `capture` ніколи не анотується:

```yaml
extensions:
  obi:
    version: '2.0'
    capture:
      policy:
        default_action: exclude
      rules:
        - action: include
          match:
            process:
              exe_path_glob:
                - /frontend
                - /backend
    correlation:
      log_trace_annotation:
        enabled: true
        match:
          - process:
              exe_path_glob:
                - /frontend
                - /backend
        plain_text:
          enabled: true
          placement: suffix
          multiline: first_line
```

Блок `plain_text` керує тим, де анотація `key=value` розміщується в не-JSON логах і які рядки багаторядкового запису її отримують. Вставлені назви полів стандартно дорівнюють `trace_id` та `span_id` і налаштовуються через `field_names`, тож вивід відповідає тому, що вже очікує ваш конвеєр логів.

У конфігурації версії 1 той самий вибір міститься під `ebpf.log_enricher.services` — деталі дивіться в [документації про кореляцію трейсів і логів](/docs/zero-code/obi/trace-log-correlation/).

Потрібна одна зміна конвеєра: для кожного збагаченого рядка оригінальний незбагачений рядок замінюється в логах контейнера на порожній рядок-заповнювач (NUL-байти), а збагачений рядок додається на його місце. Додайте у свій передавач логів фільтр, який відкидає порожні рядки-заповнювачі, — одне правило, що збігається із записами, які складаються лише з NUL.

## Перш ніж вмикати це у виробничому середовищі {#before-enabling-it-in-production}

Поведінка, яку слід врахувати у плані розгортання:

- **Великі записи розділяються.** Один виклик `write()` або `writev()`, більший за 8 KiB, не збагачується цілком: захоплений префікс видається повторно з контекстом трасування, тоді як решта потрапляє до потоку логів окремо, без збагачення — один логічний запис може стати двома. Якщо ваші сервіси регулярно виводять дуже довгі рядки логів, виміряйте перед увімкненням.
- **Розгортайте поступово.** Почніть з одного сервісу з низьким ризиком у `match` і перевірте у своєму бекенді логів дві речі: порожні рядки-заповнювачі відкидаються вашим фільтром, а рядки логів зʼявляються один раз — не дублюються й не розділяються. Потім додайте більше сервісів у `match`. Сервіси, які ви залишили поза `match`, усе одно трасуються; незмінними залишаються лише їхні логи. Відкочування — це вилучення сервісу з `match` або встановлення `enabled: false`, щоб вимкнути анотацію для всіх сервісів; застосунок залишається незмінним в обох випадках.

## Спробуйте {#try-it}

Кореляція трейсів і логів постачається в OBI. Націльте її на один сервіс, додайте фільтр рядків-заповнювачів у свій передавач логів — і ваші наявні логи, без повторної збірки чи повторного розгортання застосунку, почнуть нести ідентифікатори трейсів, які були потрібні вам під час останнього інциденту.

- Запустіть демонстрацію з цієї статті самостійно: [приклад docker compose](https://gist.github.com/mmat11/f3f23707e7bc9c94bce144f56276251d)
- [Документація OBI](/docs/zero-code/obi/)
- [Репозиторій OBI](https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation)
- Цікаво, як це працює під капотом? Внутрішня будова eBPF описана в [документації для розробників](https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/blob/6a9df076223faff5bb94ea75f15a8e24c7a1ca0d/devdocs/trace-log-correlation.md)
- Запитання чи відгуки: канал [#otel-ebpf-instrumentation](https://cloud-native.slack.com/archives/C06DQ7S2YEP) у CNCF Slack
