Розподілене трасування та APM

OxPHP підтримує поширення W3C Trace Context, експорт в OpenTelemetry (OTel) і вбудований моніторинг продуктивності застосунків (APM). Вхідні заголовки traceparent розбираються й трасування продовжується, ідентифікатори трасування доступні в PHP через $_SERVER, журнали доступу містять поля трасування, а спани можна експортувати в Jaeger, Grafana Tempo, Zipkin або будь-який OTLP-сумісний бекенд.

Плагін APM додає три рівні трасування поверх основи OTel:

  • Автоматичне інструментування — внутрішні функції PHP (PDO, mysqli, cURL, Redis, Memcached, файловий ввід-вивід) перехоплюються на рівні рушія; кожен виклик стає спаном без жодних змін у коді
  • Трасування на основі атрибутів — позначте будь-яку функцію чи метод PHP атрибутом #[OxPHP\Apm\Trace], і спани створюватимуться автоматично
  • PHP SDK — 10 функцій oxphp_apm_*() для ручного створення спанів, атрибутів, подій і запису помилок

Як це працює

  1. Вхідний запит — OxPHP читає заголовки traceparent і tracestate відповідно до специфікації W3C Trace Context
  2. Новий спан — для цього переходу генерується новий ідентифікатор спану. Вхідний ідентифікатор спану стає батьківським
  3. Передавання в PHP — ідентифікатори трасування записуються в $_SERVER['OXPHP_TRACE_ID'], $_SERVER['OXPHP_SPAN_ID'] і $_SERVER['OXPHP_PARENT_SPAN_ID']
  4. Журнал доступу — структуровані JSON-журнали містять поля trace_id і span_id для кореляції журналів
  5. Заголовки відповіді — оновлений заголовок traceparent (з ідентифікатором спану OxPHP) додається до відповіді, щоб наступні сервіси могли продовжити трасування
  6. Експорт OTel (необовʼязково) — коли плагін OTel увімкнено, кожен запит стає спаном, що експортується через OTLP з атрибутами семантичних конвенцій HTTP

Якщо заголовка traceparent немає, OxPHP генерує нові ідентифікатори трасування та спану й починає нове трасування.

Налаштування

W3C Trace Context (вбудовано)

Змінна Типове значення Опис
TRACE_CONTEXT false Увімкнути поширення W3C Trace Context. Встановіть true або 1

Плагін OpenTelemetry

Плагін OTel — це функціональність, що вмикається під час компіляції (plugin-otel). Коли його увімкнено, він автоматично вмикає поширення контексту трасування (з тим самим ефектом, що й TRACE_CONTEXT=true).

Змінна Типове значення Опис
OTEL_ENABLED false Увімкнути плагін OpenTelemetry. Булеве значення — див. Булеві значення
OTEL_EXPORTER_OTLP_PROTOCOL grpc Протокол експорту: grpc або http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT http://localhost:4317 (gRPC) або http://localhost:4318 (HTTP) Адреса колектора OTLP. З http/protobuf це базовий URL колектора, і спани надсилаються на /v1/traces під ним, як приписує специфікація OTLP: http://collector:4318 надсилає на http://collector:4318/v1/traces, тож не додавайте /v1/traces до значення. URL з https:// експортується через TLS на обох транспортах із перевіркою за системним сховищем довірених сертифікатів (образ середовища виконання має містити пакет CA, наприклад ca-certificates — офіційний образ його встановлює); власні пакети CA та mTLS поки не підтримуються
OTEL_EXPORTER_OTLP_TIMEOUT 10000 Тайм-аут експорту в мілісекундах. Через gRPC він також обмежує підключення до колектора, включно з TLS-рукостисканням, тож тримайте його більшим за час, якого потребує підключення; спроба, якій спершу потрібно підключитися, може тривати до подвоєного тайм-ауту. Через gRPC у Linux він також визначає, як швидко закривається зʼєднання, що перестало приймати дані, — див. Помилки експорту спанів у журналі
OTEL_EXPORTER_OTLP_HEADERS (не задано) Заголовки автентифікації: key=value,key2=value2
OTEL_SERVICE_NAME oxphp Назва сервісу в експортованих спанах
OTEL_SERVICE_VERSION (не задано) Атрибут версії сервісу
OTEL_RESOURCE_ATTRIBUTES (не задано) Додаткові атрибути ресурсу: env=prod,region=us-east-1
OTEL_TRACES_SAMPLER parentbased_traceidratio Стратегія семплювання: always_on, always_off, traceidratio, parentbased_always_on, parentbased_always_off, parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG 1.0 Частка семплювання (0.0–1.0) для семплерів на основі частки
Note

Некоректні значення OTEL_TRACES_SAMPLER_ARG або значення поза діапазоном обмежуються до [0.0, 1.0] і записуються в журнал з рівнем warn. Невідомі значення OTEL_TRACES_SAMPLER замінюються на parentbased_traceidratio із записом у журнал.

Плагін APM

Плагін APM — це функціональність, що вмикається під час компіляції (plugin-apm) і залежить від плагіна OTel. Він додає автоматичне інструментування, декоратор #[OxPHP\Apm\Trace] і PHP SDK для трасування.

Змінна Типове значення Опис
OTEL_APM_ENABLED false Увімкнути APM: автоматичне інструментування, перехоплення помилок, PHP SDK. Потребує OTEL_ENABLED=true. Булеве значення — див. Булеві значення
OTEL_APM_SLOW_QUERY_MS 100 Поріг повільного запиту в мілісекундах. Запити, що його перевищують, отримують oxphp.db.slow=true у своїх спанах
OTEL_APM_DB_CAPTURE_PARAMS_ENABLED false Записувати привʼязані параметри в атрибут спану db.params. Значення записуються як є — на відміну від db.statement, вони не знеособлюються, тож у ваші трасування можуть потрапити персональні дані (адреси електронної пошти, токени тощо). Вмикайте лише там, де це прийнятно. Булеве значення — див. Булеві значення
OTEL_APM_STACKTRACE_MAX_BYTES 8192 Максимальний розмір атрибута exception.stacktrace у байтах. Понад цю межу стек викликів обрізається з кінця (кореневий кадр зберігається) з позначкою …(truncated). 0 вимикає обрізання
OTEL_APM_MESSAGE_MAX_BYTES 4096 Максимальний розмір атрибута exception.message у байтах (типове значення відповідає ліміту New Relic на значення одного атрибута). Понад цю межу повідомлення обрізається з кінця з позначкою …(truncated). 0 вимикає обрізання

Контекст трасування в PHP

Коли TRACE_CONTEXT=true, у ваших PHP-скриптах доступні три змінні $_SERVER:

Змінна Опис Приклад
OXPHP_TRACE_ID Ідентифікатор трасування W3C (32 шістнадцяткові символи) 4bf92f3577b34da6a3ce929d0e0e4736
OXPHP_SPAN_ID Ідентифікатор спану OxPHP для цього запиту (16 шістнадцяткових символів) 00f067aa0ba902b7
OXPHP_PARENT_SPAN_ID Ідентифікатор вхідного батьківського спану (16 шістнадцяткових символів, порожній для нового трасування) a3ce929d0e0e4736

Використовуйте їх, щоб передавати контекст трасування наступним сервісам:

php
<?php $traceId = $_SERVER['OXPHP_TRACE_ID'] ?? ''; $spanId = $_SERVER['OXPHP_SPAN_ID'] ?? ''; if ($traceId) { // Build a traceparent header for downstream calls $traceparent = "00-{$traceId}-{$spanId}-01"; $response = file_get_contents('https://api.example.com/data', false, stream_context_create([ 'http' => [ 'header' => "traceparent: {$traceparent}\r\n", ], ]) ); }

З Guzzle

php
<?php $traceId = $_SERVER['OXPHP_TRACE_ID'] ?? ''; $spanId = $_SERVER['OXPHP_SPAN_ID'] ?? ''; $client = new \GuzzleHttp\Client(); $response = $client->get('https://api.example.com/users', [ 'headers' => [ 'traceparent' => "00-{$traceId}-{$spanId}-01", ], ]);

Кореляція журналів доступу

Коли контекст трасування увімкнено, структуровані JSON-журнали доступу містять поля trace_id і span_id:

json
{ "timestamp": "2026-03-23T10:15:30.123Z", "level": "INFO", "fields": { "request_id": "4bf92f3577b34da6-00f067aa", "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736", "span_id": "00f067aa0ba902b7", "method": "GET", "path": "/api/users", "status": 200, "duration_us": 1523, "remote_ip": "10.0.0.1", "user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0", "message": "request completed" } }

Після цього в системах агрегації журналів (Loki, Elasticsearch, Splunk, CloudWatch) можна шукати за ідентифікатором трасування й знаходити всі записи журналу для розподіленого трасування.

Заголовки відповіді

OxPHP додає до кожної відповіді заголовок traceparent із власним ідентифікатором спану OxPHP:

http
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

Якщо вхідний запит містив заголовок tracestate, він також передається у відповіді.

Інтеграція з OpenTelemetry

Коли плагін OTel увімкнено, кожен HTTP-запит стає спаном, що експортується у ваш бекенд трасування через OTLP.

Атрибути спанів

Експортовані спани містять стандартні атрибути семантичних конвенцій HTTP:

Атрибут Опис
http.request.method HTTP-метод (GET, POST тощо)
url.path Шлях запиту
http.response.status_code Код статусу відповіді
client.address IP-адреса клієнта
user_agent.original Заголовок User-Agent запиту (перший, якщо їх надіслано кілька), обрізаний до 512 байтів (разом із позначкою …(truncated)), якщо він довший. Байти, що не є коректним UTF-8, замінюються на U+FFFD. Відсутній, якщо запит його не надіслав
server.address Адреса, яку прослуховує сервер
oxphp.request_id Ідентифікатор запиту OxPHP
http.request.body.size Розмір тіла запиту в байтах (якщо ненульовий)
http.response.body.size Розмір тіла відповіді в байтах (якщо ненульовий)
oxphp.shed_reason Причина, з якої механізм допуску відхилив запит, якщо він його відхилив: queue_full, wait_timeout, waiting_full або waiting_bytes для 529, shutting_down для 503, pool_unavailable для 500 — значення мітки reason метрики oxphp_admission_refused_total. В інших випадках відсутній, зокрема для 529, яку повернув сам застосунок

Відповіді 5xx позначаються як спани з помилкою.

Події спанів

Дочірні спани також містять події спанів — позначені часом анотації, що експортуються як події OpenTelemetry і нативно відображаються в Jaeger, Grafana Tempo та інших OTLP-бекендах. Атрибут oxphp.event.kind кожної події визначає її тип:

oxphp.event.kind Джерело Атрибути події
exception Функція з #[OxPHP\Apm\Trace], що кинула виняток, oxphp_apm_error() або необроблений виняток / фатальна помилка в кореневому спані запиту (див. Автоматичне захоплення винятків у кореневому спані) exception.type, exception.message, exception.stacktrace
custom oxphp_apm_event() задає користувач
mark анотація профайлера #[Mark] задає користувач
slow перевищення порогу #[SlowThreshold] профайлера threshold_ms, elapsed_ms
memory_spike перевищення порогу #[MemoryThreshold] профайлера threshold_kb, delta_bytes

Атрибут oxphp.event.kind може також мати значення sql, http або alloc у подіях, які генерує інструментування APM.

Автоматичне захоплення винятків у кореневому спані

Коли запит завершується необробленим винятком або фатальною помилкою й повертає 5xx, OxPHP автоматично додає подію exception до кореневого спану запиту — без атрибута #[OxPHP\Apm\Trace] і без виклику oxphp_apm_error(). Помилка 500 стає самоописовою в трасуванні, а бекенди, що групують помилки за подією винятку (наприклад, New Relic Errors Inbox), показують її без жодного коду в застосунку.

Подія містить стандартні exception.type, exception.message і exception.stacktrace, а також два розширення OxPHP — exception.file і exception.line, що вказують на місце, де кинуто виняток (або де сталася фатальна помилка). Для фатальної помилки без класу — trigger_error(…, E_USER_ERROR), аварійного завершення через нестачу памʼяті чи перевищення часу виконання — exception.type є синтетичною назвою (константою помилки PHP, наприклад E_USER_ERROR), а стека викликів немає. Виклик невизначеної функції в PHP 8 не є фатальною помилкою без класу: він кидає звичайний Error, який містить повний стек викликів, як і будь-який інший виняток. Повідомлення та стек викликів підпадають під ті самі обмеження OTEL_APM_MESSAGE_MAX_BYTES / OTEL_APM_STACKTRACE_MAX_BYTES, що й інші події винятків.

Це працює для чистого PHP, застосунків без власного обробника винятків і обробників у режимі воркера.

Межа: фреймворки, що поглинають винятки (традиційний шлях обробки запиту). У режимах Traditional / Framework / SPA застосунок, який встановлює set_exception_handler() і рендерить власну сторінку помилки (Laravel, Symfony, WordPress, …), з погляду рушія обробив виняток. Той ніколи не поширюється як неперехоплений, тож OxPHP бачить лише статус 500 і не може відновити Throwable. Для таких запитів автоматичне захоплення не спрацьовує; натомість записуйте виняток явно з модуля звітування про помилки вашого фреймворку за допомогою oxphp_apm_error($e).

Режим воркера не проходить через set_exception_handler(). Середовище виконання воркера перехоплює виняток, що вийшов за межі вашого замикання oxphp_worker(), напряму, не викликаючи користувацький обробник винятків рушія. Тому для обробників воркера автоматичне захоплення спрацьовує щоразу, коли виняток залишає замикання, навіть якщо код зареєстрував власний set_exception_handler() — цей обробник тоді застосовується лише до винятків, які перехоплює саме замикання, а не до тих, що виходять за його межі.

Потокові відповіді. Для потокової відповіді (SSE або будь-який цикл з oxphp_stream_flush()) HTTP-статус фіксується, щойно заголовки відправлено, і від цієї миті запит вважається завершеним. Фатальна помилка, що сталася після фіксації статусу — у потоковій відповіді або у відповіді, яка викликала oxphp_finish_request(), — лише записується в журнал; вона не додається до кореневого спану (трасування все одно показує спан, але без події exception). Це задокументована межа, і вона однаково діє як для зафіксованого 5xx, так і для зафіксованого 2xx.

Ідентифікатор запиту з OTel

Коли плагін OTel активний, ідентифікатори запитів виводяться з контексту трасування: перші 16 символів ідентифікатора трасування та перші 8 символів ідентифікатора спану, розділені дефісом. Він зʼявляється в журналах, у заголовку відповіді X-Request-ID і в oxphp_request_id() у PHP.

APM: автоматичне інструментування

Коли плагін APM увімкнено, OxPHP автоматично перехоплює 34 внутрішні функції PHP на рівні рушія. Кожен виклик перехопленої функції створює дочірній спан під кореневим спаном поточного запиту — без жодних змін у коді.

Перехоплювані функції

Категорія Функції
PDO PDO::__construct, PDO::query, PDO::exec, PDO::prepare, PDOStatement::execute
mysqli mysqli::__construct, mysqli::query, mysqli::prepare, mysqli_stmt::prepare, mysqli_stmt::execute
cURL curl_init, curl_setopt, curl_exec, curl_multi_exec
Redis Redis::connect, Redis::get, Redis::set, Redis::del, Redis::mget, Redis::mset, Redis::hget, Redis::hset, Redis::lpush, Redis::rpush
Memcached Memcached::get, Memcached::set, Memcached::delete, Memcached::getMulti, Memcached::setMulti
Файловий ввід-вивід fopen, fread, fwrite, file_get_contents, file_put_contents

Перехоплювачі встановлюються лише для фактично завантажених розширень. Якщо ваша збірка не містить розширення Redis, перехоплювачі Redis мовчки пропускаються.

Атрибути спанів бази даних

Перехоплювачі бази даних (PDO, mysqli) доповнюють свої спани атрибутами семантичних конвенцій OpenTelemetry:

Атрибут Джерело Приклад
db.statement Текст запиту, у якому літеральні значення замінено на ?, тож персональні дані (адреси електронної пошти, токени) ніколи не потрапляють у бекенд трасування SELECT * FROM users WHERE email = ?
db.operation Перше ключове слово SQL SELECT
db.system Визначається з DSN PDO / конструктора mysqli mysql, postgresql, sqlite
server.address, server.port Хост/порт зʼєднання (відсутні для unix-сокета чи файлу SQLite) db.internal, 5432
db.name Назва бази даних або шлях до файлу для SQLite shop
oxphp.db.slow true, коли реальний час виклику досягає OTEL_APM_SLOW_QUERY_MS або перевищує його true
db.params Привʼязані параметри, записані як є (без знеособлення) — тож вони можуть містити персональні дані; лише коли OTEL_APM_DB_CAPTURE_PARAMS_ENABLED=true [1, active]

db.statement зчитується з аргументів кожного виклику query / exec / prepare, тому він присутній у спані саме цього виклику. У спані PDOStatement::execute він теж присутній — зчитаний із власної властивості queryString обʼєкта запиту, тож ніколи не може виявитися SQL іншого запиту. Спан mysqli_stmt::execute не має db.statement (mysqli не надає такої властивості), але SQL є в попередньому спані mysqli::prepare. Кожен спан execute містить тривалість, а разом із нею й позначку повільного запиту, а для PDO — також db.params. Перехоплювачі кешу (Redis, Memcached), HTTP-клієнта (cURL) і файлового вводу-виводу створюють простий спан лише з тривалістю.

Встановлення перехоплювачів

Для потокобезпеки в PHP ZTS перехоплювачі встановлюються у дві фази:

  1. Фаза 1 (MINIT) — під час ініціалізації модуля OxPHP перевіряє кожну цільову функцію щодо завантажених розширень і зберігає вказівник на обробник, який кожна цільова функція має на цей момент, у незмінний список схвалених
  2. Фаза 2 (RINIT) — під час першого запиту в кожному робочому потоці схвалені перехоплювачі встановлюються в таблиці функцій цього потоку

Так кожен робочий потік ZTS має узгоджені зміни таблиць функцій і власний локальний стан.

Спани можуть включати очікування на зʼєднання

Фаза 1 виконується після власних замін обробників OxPHP. Тому коли RUNTIME_HOOKS вмикає streams, обробник, захоплений для методу PDO, mysqli чи Redis, який ця категорія також перехоплює, — це захищений обробник OxPHP: для запиту чи команди він захоплює зʼєднання для файбера, що його викликає, а для PDO::__construct серіалізує постійні конструктори до того самого джерела даних (див. Хуки середовища виконання). Спан обгортає цей захист. Усередині файберів запитів у режимі воркера та файберів асинхронних завдань, де захист може чекати, тривалість спану (а для запиту до бази даних — і позначка повільного запиту) включає будь-яке очікування на зʼєднання, яке використовує інший запит чи завдання.

APM: трасування на основі атрибутів

Атрибут #[OxPHP\Apm\Trace] автоматично створює спани навколо позначених функцій і методів. На відміну від перехоплювачів автоматичного інструментування (які націлені на внутрішні функції C), він працює з користувацьким кодом PHP.

php
<?php use OxPHP\Apm\Trace; #[Trace] function processOrder(int $orderId): void { // A span named "processOrder" is created on entry and closed on exit. // If an exception is thrown, the span is marked as error and an // "exception" span event records exception.type, exception.message // and exception.stacktrace. } class PaymentService { #[Trace] public function charge(float $amount): bool { // Span named "PaymentService::charge" return true; } }

Атрибут #[Trace] застосовується і до функцій, і до методів. Жодного виклику реєстрації не потрібно — плагін APM реєструє декоратор автоматично під час ініціалізації.

Якщо позначена функція кидає виняток, статус спану встановлюється в error і записується подія exception з повними даними семантичних конвенцій OpenTelemetry: exception.type (клас), exception.message (повідомлення) і exception.stacktrace (стек викликів із getTraceAsString()). Повідомлення обрізається до OTEL_APM_MESSAGE_MAX_BYTES байтів (типово 4096), а стек викликів — до OTEL_APM_STACKTRACE_MAX_BYTES байтів (типово 8192); 0 вимикає будь-яке з обмежень. Захоплення аргументів у кадрах відповідає власному налаштуванню PHP zend.exception_ignore_args.

APM: PHP SDK для трасування

Плагін APM реєструє 10 функцій oxphp_apm_*() для ручного керування спанами. Усі вони реєструються незалежно від того, чи увімкнено APM, тож ваш код працює без змін у будь-якому середовищі. Коли APM вимкнено, функції спанів нічого не роблять і повертають сторожові значення (0 для ідентифікаторів, "" для рядків).

Функція трасування завжди виконує свій callback

Єдиний виняток із правила «нічого не робити» — oxphp_apm_trace(): коли APM вимкнено, вона все одно виконує переданий їй callback і повертає його значення. Тож бізнес-логіка всередині callback продовжує працювати й тоді, коли трасування вимкнено.

Створення спанів

php
<?php // Start a span and get its local ID $spanId = oxphp_apm_start('cache.warm', ['cache.size' => '1024']); // ... do work ... // Close the span oxphp_apm_end($spanId);

Додавання атрибутів і подій

php
<?php $spanId = oxphp_apm_start('order.process'); // Add attributes to the current span (or a specific one) oxphp_apm_attribute('order.id', $orderId); oxphp_apm_attribute('order.total', $total, $spanId); // Record an event on the span oxphp_apm_event('payment.authorized', [ 'provider' => 'stripe', 'amount' => (string) $amount, ]); oxphp_apm_end($spanId);

Запис помилок

php
<?php $spanId = oxphp_apm_start('external.api'); try { $result = callExternalApi(); } catch (\Throwable $e) { // Mark the span as error oxphp_apm_error($e, $spanId); throw $e; } finally { oxphp_apm_end($spanId); }

Поширення контексту трасування

php
<?php // Get the current trace ID and span ID $traceId = oxphp_apm_trace_id(); $currentSpanId = oxphp_apm_span_id(); // Or get a ready-to-use traceparent header value $traceparent = oxphp_apm_header(); // "00-{trace_id}-{span_id}-01" // Propagate to downstream services $response = file_get_contents('https://api.example.com/data', false, stream_context_create([ 'http' => [ 'header' => "traceparent: {$traceparent}\r\n", ], ]) );

Довідник функцій

Функція Повертає Опис
oxphp_apm_trace(name, callback, ?attributes) mixed Виконати callback усередині спану й повернути його значення. Callback отримує локальний ідентифікатор спану; кинутий виняток позначає спан як помилку з подією exception (тип і повідомлення, без стека викликів) і поширюється далі
oxphp_apm_start(name, ?attributes) int Відкрити спан і повернути його локальний ідентифікатор. 0, коли APM вимкнено
oxphp_apm_end(span_id) void Закрити спан із заданим локальним ідентифікатором
oxphp_apm_attribute(key, value, ?span_id) void Встановити атрибут для поточного або вказаного спану
oxphp_apm_event(name, ?attributes, ?span_id) void Записати позначену часом подію в поточному або вказаному спані
oxphp_apm_error(exception, ?span_id) void Позначити поточний або вказаний спан як помилку й записати подію exception. Обʼєкт Throwable дає exception.type, exception.message і exception.stacktrace; простий рядковий аргумент записується як exception.message із загальним exception.type зі значенням Error (щоб подія залишалася видимою в бекендах, які групують за типом)
oxphp_apm_status(code, ?description, ?span_id) void Встановити статус спану: 0 = Unset, 1 = Ok, 2 = Error
oxphp_apm_trace_id() string Поточний ідентифікатор трасування (32 шістнадцяткові символи). Порожній, коли APM вимкнено
oxphp_apm_span_id() string Поточний ідентифікатор спану (16 шістнадцяткових символів). Порожній, коли немає активного спану
oxphp_apm_header() string Значення заголовка W3C traceparent для контексту поточного спану

Повний довідник сигнатур функцій див. у розділі Функції PHP.

Приклад для Docker

Готові до запуску варіанти compose.yaml:

Увімкніть поширення трасування W3C без зовнішнього бекенду:

compose.yaml
services: app: image: ghcr.io/oxphp/oxphp:0.13.0 ports: - "80:80" environment: - TRACE_CONTEXT=true - INTERNAL_ADDR=0.0.0.0:9090
Note

Функціональність Cargo plugin-apm має бути увімкнена під час збирання. Офіційний образ OxPHP містить її за замовчуванням.

Стек спостережуваності

OxPHP надає три опори спостережуваності, які працюють разом:

Опора Можливість Кореляція
Метрики Лічильники та гістограми Prometheus на /metrics Агреговані дані про продуктивність
Журналювання Структуровані JSON-журнали доступу з ACCESS_LOG Подробиці кожного запиту з пошуком за trace_id
Трасування W3C Trace Context + експорт OTLP Наскрізний розподілений потік запиту

Усі три мають спільні trace_id і request_id, тож можна перейти від сповіщення на дашборді Grafana до трасування в Tempo, а звідти — до рядків журналу Loki для окремого запиту.

Усунення несправностей

Заголовки трасування не зʼявляються у відповідях

TRACE_CONTEXT не увімкнено.

Виправлення: встановіть TRACE_CONTEXT=true або увімкніть плагін OTel через OTEL_ENABLED=true (він автоматично вмикає контекст трасування).

Змінні трасування в $_SERVER порожні

Контекст трасування вимкнено або змінні перевіряються поза OxPHP.

Перевірте: змінні OXPHP_TRACE_ID, OXPHP_SPAN_ID і OXPHP_PARENT_SPAN_ID існують лише тоді, коли TRACE_CONTEXT=true і запит обслуговує OxPHP. Перевірити можна так:

php
<?php echo $_SERVER['OXPHP_TRACE_ID'] ?? 'trace context not enabled';
Спани не зʼявляються в Jaeger/Tempo

Перевірте: чи доступна адреса OTLP з контейнера OxPHP:

bash
docker compose exec app curl -v http://jaeger:4317

Перевірте: чи увімкнено плагін:

bash
curl -s http://localhost:9090/config | jq '.plugins'

Виправлення: переконайтеся, що встановлено OTEL_ENABLED=true, а OTEL_EXPORTER_OTLP_ENDPOINT вказує на правильну адресу колектора — для http/protobuf це його базовий URL без /v1/traces. Якщо журнал показує невдалі експорти, розділ Помилки експорту спанів у журналі пояснює, що означає кожен із них.

Надмірний обсяг семплювання в продакшені

Експорт кожного спану дорого обходиться за великого трафіку.

Виправлення: зменште частку семплювання:

bash
OTEL_TRACES_SAMPLER=parentbased_traceidratio OTEL_TRACES_SAMPLER_ARG=0.1 # Sample 10% of traces

Семплювання на основі батьківського спану означає, що коли вхідний запит несе семпльоване трасування, воно завжди семплюється незалежно від частки. Нові трасування, розпочаті в OxPHP, семплюються із заданою частотою. Якщо OTEL_TRACES_SAMPLER має невідоме значення, OxPHP записує в журнал попередження й повертається до parentbased_traceidratio.

Помилки експорту спанів у журналі

OxPHP надсилає спани колектору пакетами й записує невдалий експорт у журнал окремими рядками, у яких plugin має значення otel:

  • WARN OTLP span export failed, retrying once — перша спроба швидко завершилася невдачею, не отримавши відповіді від колектора, і пакет надсилається знову. Поля: spans (розмір пакета), error.
  • ERROR OTLP span export failed, the batch is dropped — експорт остаточно не вдався, і його спани більше не надсилаються. Поля: spans, retried, error.

Рядок WARN, за яким не йде рядок ERROR, означає, що повторна спроба доставила пакет. Помилки, якими відповідає сам колектор, не повторюються: вони надходять одним рядком ERROR, у якому retried має значення false.

Кожен рядок ERROR супроводжує другий рядок для того самого експорту, який записує бібліотека OpenTelemetry. Його message порожнє, а причина міститься в error:

json
{"level":"ERROR","fields":{"message":"","name":"BatchSpanProcessor.ExportError","error":"Operation failed: code: 'Unknown error', message: \"transport error\", source: ..."}}

На що вказує поле error:

У error Що сталося
code: 'The request does not have valid authentication credentials' (gRPC), kind: Status(401, None) (http/protobuf) Колектор відхилив API-ключ
ConnectionReset (будь-який протокол), connection closed (gRPC), IncompleteMessage (http/protobuf) Щось між OxPHP і колектором, наприклад балансувальник навантаження чи NAT, розірвало зʼєднання, поки воно простоювало
kind: TimedOut, error: Elapsed (gRPC) Експорту довелося спершу підключитися, і підключення — розвʼязання імені, TCP і TLS-рукостискання для адреси https:// — не завершилося в межах OTEL_EXPORTER_OTLP_TIMEOUT. Коли імʼя розвʼязується в кілька адрес, тайм-аут ділиться між ними, тож це може також означати, що одна з адрес не відповіла в межах своєї частки

Експорт через зʼєднання, дальній бік якого зник без скидання (наприклад, зʼєднання, яке NAT чи балансувальник навантаження мовчки розірвав), упирається в OTEL_EXPORTER_OTLP_TIMEOUT, як і будь-який експорт, на який колектор не відповідає вчасно. Через gRPC у Linux зʼєднання тоді закривається, щойно надіслане через нього залишається непідтвердженим упродовж цього часу, а наступні експорти підключаються знову. Зʼєднання gRPC також використовує TCP keepalive — перша проба після 30 секунд без трафіку, далі по одній кожні 10 секунд. Keepalive утримує зʼєднання в таблицях NAT і балансувальника навантаження між експортами та, за типового тайм-ауту, закриває мертве зʼєднання, що простоює, приблизно за хвилину.

Метрики експорту OpenTelemetry рахують невдалі експорти, їхні спани та повторні спроби й пояснюють, коли експорт повторюється.

Промпти для вашого агента

Ці промпти розраховані на агента з підключеним MCP-сервером документації OxPHP — потрібні сторінки він прочитає сам.

prompt
Експортуй трасування OxPHP у мій бекенд трасування. Прочитай сторінку `features/distributed-tracing` через MCP-сервер документації OxPHP. З моїх файлів Compose або Kubernetes зʼясуй, де розташований колектор, дай мені блок змінних середовища та частоту семплювання, яку витримає мій трафік, і покажи, де вихідні виклики в цьому репозиторії мають передавати контекст трасування.
prompt
Мої спани OxPHP не доходять до бекенду трасування. Прочитай сторінку `features/distributed-tracing` через MCP-сервер документації OxPHP. Перевір перемикачі плагінів, адресу та протокол OTLP, а також семплер, який задає моє розгортання, і скажи, на якому переході губляться трасування.

Дивіться також