Распределённая трассировка и 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_*()для ручного создания спанов, атрибутов, событий и записи ошибок
Как это работает
- Входящий запрос — OxPHP читает заголовки
traceparentиtracestateв соответствии со спецификацией W3C Trace Context - Новый спан — для этого перехода генерируется новый идентификатор спана. Входящий идентификатор спана становится родительским
- Передача в PHP — идентификаторы трассировки помещаются в
$_SERVER['OXPHP_TRACE_ID'],$_SERVER['OXPHP_SPAN_ID']и$_SERVER['OXPHP_PARENT_SPAN_ID'] - Журнал доступа — структурированные JSON-логи содержат поля
trace_idиspan_idдля корреляции логов - Заголовки ответа — обновлённый заголовок
traceparent(с идентификатором спана OxPHP) добавляется в ответ, чтобы нижестоящие сервисы могли продолжить трассировку - Экспорт в 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. URL с https:// экспортируется по TLS на обоих транспортах, с проверкой по системному хранилищу доверенных сертификатов (образ среды выполнения должен содержать CA-бандл, например ca-certificates — официальный образ его устанавливает); пользовательские CA-бандлы и mTLS пока не поддерживаются |
OTEL_EXPORTER_OTLP_TIMEOUT |
10000 |
Таймаут экспорта в миллисекундах |
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) для сэмплеров на основе коэффициента |
Недопустимые или выходящие за границы значения 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, они не обфусцируются, поэтому в трассировках могут оказаться персональные данные (email-адреса, токены и т. п.). Включайте только там, где это приемлемо. Булево значение — см. Булевы значения |
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 hex-символа) | 4bf92f3577b34da6a3ce929d0e0e4736 |
OXPHP_SPAN_ID |
Идентификатор спана OxPHP для этого запроса (16 hex-символов) | 00f067aa0ba902b7 |
OXPHP_PARENT_SPAN_ID |
Входящий родительский идентификатор спана (16 hex-символов, пусто для новой трассировки) | a3ce929d0e0e4736 |
Используйте их для передачи контекста трассировки нижестоящим сервисам:
<?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
$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:
{
"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",
"message": "request completed"
}
}После этого вы можете искать логи по идентификатору трассировки в системах агрегации логов (Loki, Elasticsearch, Splunk, CloudWatch), чтобы найти все записи логов для распределённой трассировки.
Заголовки ответа
OxPHP добавляет заголовок traceparent в каждый ответ со своим собственным идентификатором спана:
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-адрес клиента |
server.address |
Адрес прослушивания сервера |
oxphp.request_id |
Идентификатор запроса OxPHP |
http.request.body.size |
Размер тела запроса в байтах (если ненулевой) |
http.response.body.size |
Размер тела ответа в байтах (если ненулевой) |
Ответы 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-статус фиксируется, как только заголовки ушли в сеть, и запрос с этого момента считается завершённым. Фатал, выброшенный после фиксации статуса — на потоковом ответе или на ответе, вызвавшем 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 |
Текст запроса с литеральными значениями, заменёнными на ?, чтобы персональные данные (email-адреса, токены) никогда не попадали в бэкенд трассировки |
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, когда wall-time вызова достигает или превышает 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 (MINIT) — во время инициализации модуля OxPHP проверяет каждую целевую функцию по загруженным расширениям и сохраняет указатели на оригинальные обработчики в доступный только для чтения список разрешённых
- Фаза 2 (RINIT) — при первом запросе в каждом потоке воркера разрешённые перехватчики устанавливаются в таблицы функций этого потока
Это гарантирует, что каждый поток воркера ZTS имеет согласованные изменения таблицы функций и потоколокальное состояние.
APM: трассировка на основе атрибутов
Атрибут #[OxPHP\Apm\Trace] автоматически создаёт спаны вокруг декорированных функций и методов. В отличие от перехватчиков автоматического инструментирования (которые нацелены на внутренние функции на C), это работает с пользовательским кодом 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 отключён, поэтому ваш код работает без изменений в любом окружении.
Создание спанов
<?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
$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
$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
// 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) |
void |
Выполнить callback внутри спана (зарезервировано для будущего использования) |
oxphp_apm_start(name, ?attributes) |
int |
Открыть спан и вернуть его локальный ID. 0, когда APM отключён |
oxphp_apm_end(span_id) |
void |
Закрыть спан с указанным локальным ID |
oxphp_apm_attribute(key, value, ?span_id) |
void |
Установить атрибут на текущем или указанном спане |
oxphp_apm_event(name, ?attributes, ?span_id) |
void |
Записать событие с временной меткой на текущем или указанном спане |
oxphp_apm_error(exception, ?span_id) |
void |
Пометить текущий или указанный спан как error и записать событие 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 hex-символа). Пусто, когда APM отключён |
oxphp_apm_span_id() |
string |
Текущий идентификатор спана (16 hex-символов). Пусто, когда нет активного спана |
oxphp_apm_header() |
string |
Значение заголовка W3C traceparent для текущего контекста спана |
Полный справочник сигнатур функций см. в разделе Функции PHP.
Пример Docker
Готовые к запуску варианты compose.yaml:
Включить распространение трассировки W3C без внешнего бэкенда:
services:
app:
image: ghcr.io/oxphp/oxphp:0.11.0
ports:
- "80:80"
environment:
- TRACE_CONTEXT=true
- INTERNAL_ADDR=0.0.0.0:9090Полный стек наблюдаемости с Jaeger в качестве бэкенда трассировки:
services:
app:
image: ghcr.io/oxphp/oxphp:0.11.0
ports:
- "80:80"
environment:
- OTEL_ENABLED=true
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317
- OTEL_SERVICE_NAME=my-app
- OTEL_SERVICE_VERSION=1.0.0
- OTEL_RESOURCE_ATTRIBUTES=env=production
- INTERNAL_ADDR=0.0.0.0:9090
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPCservices:
app:
image: ghcr.io/oxphp/oxphp:0.11.0
ports:
- "80:80"
environment:
- OTEL_ENABLED=true
- OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4317
- OTEL_SERVICE_NAME=my-app
tempo:
image: grafana/tempo:latest
ports:
- "4317:4317"
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"Полная наблюдаемость с автоматическим инструментированием запросов к базе данных, HTTP-вызовов, операций с кэшем и файлового ввода-вывода:
services:
app:
image: ghcr.io/oxphp/oxphp:0.11.0
ports:
- "80:80"
environment:
- OTEL_ENABLED=true
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317
- OTEL_SERVICE_NAME=my-app
- OTEL_APM_ENABLED=true
- OTEL_APM_SLOW_QUERY_MS=50
- INTERNAL_ADDR=0.0.0.0:9090
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPC
environment:
- COLLECTOR_OTLP_ENABLED=trueФункция 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
echo $_SERVER['OXPHP_TRACE_ID'] ?? 'trace context not enabled';Спаны не появляются в Jaeger/Tempo
Проверка: Убедитесь, что эндпоинт OTLP доступен из контейнера OxPHP:
docker compose exec app curl -v http://jaeger:4317Проверка: Убедитесь, что плагин включён:
curl -s http://localhost:9090/config | jq '.plugins'Решение: Убедитесь, что OTEL_ENABLED=true и OTEL_EXPORTER_OTLP_ENDPOINT указывает на правильный адрес коллектора.
Большой объём сэмплирования в продакшене
Экспорт каждого спана дорого обходится при высоких объёмах трафика.
Решение: Уменьшите коэффициент сэмплирования:
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1 # Sample 10% of tracesСэмплирование на основе родителя означает, что если входящий запрос несёт сэмплированную трассировку, она всегда будет сэмплироваться независимо от коэффициента. Новые трассировки, начатые в OxPHP, сэмплируются с настроенной частотой. Если OTEL_TRACES_SAMPLER установлен в неизвестное значение, OxPHP записывает предупреждение в лог и откатывается к parentbased_traceidratio.
См. также
- Функции PHP — справочник функций
oxphp_apm_*() - Декораторы — перехват функций на основе атрибутов, включая
#[Trace] - Логирование доступа — структурированные JSON-логи с полями трассировки
- Идентификаторы запросов — как идентификаторы запросов взаимодействуют с контекстом трассировки
- Метрики — справочник метрик Prometheus
- Проверки работоспособности — эндпоинт
/config, показывающий статус контекста трассировки - Справочник конфигурации — все переменные окружения