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

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

OpenTelemetry eBPF Instrumentation (OBI) тепер може додавати цей відсутній контекст трасування до логів, які ваші сервіси вже пишуть. Ваші застосунки не змінюються: без SDK, без налаштування бібліотеки логування, без повторної збірки чи повторного розгортання застосунку.

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

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

Що змінюється під час інциденту

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

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

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

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

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

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

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 одного трейсу

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

Збагачені логи з обох сервісів, що несуть той самий ідентифікатор трейсу

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

Чи підходить це для вашого середовища?

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

  • Місце призначення логів. Збагачення охоплює логи, записані в 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 поки що не збагачуються; робочі навантаження на платформних потоках це не зачіпає.
  • Сервіси, інструментовані за допомогою SDK OTel. Збагачення працює й там і є корисним, коли SDK експортує трейси, але не логи: OBI вставляє лише trace_id, бо згенеровані OBI ідентифікатори відрізків не збігалися б з ідентифікаторами SDK — а хибне посилання на відрізок гірше за його відсутність. Ви все одно можете знайти транзакцію в логах за ідентифікатором трейсу.

Як увімкнути

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

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 — деталі дивіться в документації про кореляцію трейсів і логів.

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

Перш ніж вмикати це у виробничому середовищі

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

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

Спробуйте

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

Востаннє змінено October 15, 2026: [uk] Blog OBI trace-log correlation (1b629805)