Перейти к содержимому

Telemetry: мост OpenTelemetry и no-op-резерв

Модуль Telemetry — это необязательный мост движка к OpenTelemetry SDK (OTel). Когда SDK установлен, модуль формирует span и метрики с очищенными атрибутами. Когда SDK отсутствует, полный набор no-op-объектов трассировщика и измерителя оставляет вызовы инструментирования корректными и практически бесплатными. Инструментирование можно безопасно оставлять в коде.

Окно терминала
composer require nextpdf/core:^3

Проектная цель — наблюдаемость с нулевыми затратами, когда SDK отсутствует. Горячие пути движка вызывают трассировщик и измеритель без предварительной проверки. Делают ли эти вызовы реальную работу, зависит от среды выполнения, а не от условной проверки в каждом месте вызова.

OpenTelemetryInterceptor — это мост. isAvailable() сообщает, присутствует ли OTel SDK. startSpan(string $name, array $attributes = []) / endSpan(?object $span) оборачивают трассируемую операцию, а recordMetric() записывает значение счётчика или датчика. Когда OTel отсутствует, перехватчик сообщает, что он недоступен, и вызовы остаются бездействующими. TelemetryBridge связывает перехватчик с точками наблюдения движка.

AttributeSanitizer — это уровень защиты. sanitize(array $attributes) очищает карту атрибутов, прежде чем она покинет процесс. Атрибуты телеметрии — распространённый канал случайной утечки персональных данных (PII), поэтому очистка является частью контракта, а не дополнением. Очиститель, перехватчик и мост помечены как @since 2.3.0.

NullTracer, NullSpanBuilder, NullSpan, NullMeter, NullCounter и NullHistogram — это no-op-резерв. Они соответствуют формам вызовов, которые предоставляет OTel SDK: spanBuilder(), setAttribute() (с цепочкой вызовов), startSpan(), end(), createHistogram(), createUpDownCounter(), add() и record(). Они ничего не делают. Поскольку резерв полный, инструментированный код не ветвится по доступности; он вызывает трассировщик, а no-op поглощает вызов.

КлассКлючевые членыРоль
OpenTelemetryInterceptorisAvailable(), startSpan(), endSpan(), recordMetric()Мост OTel для span и метрик (@since 2.3.0)
TelemetryBridgeсвязь с движкомСвязывает перехватчик с точками наблюдения движка (@since 2.3.0)
AttributeSanitizersanitize(array $attributes): arrayОчищает атрибуты для защиты от утечки PII (@since 2.3.0)
NullTracerspanBuilder(string $name): NullSpanBuilderNo-op-трассировщик
NullSpanBuildersetAttribute(), startSpan(): NullSpanNo-op-построитель span (с цепочкой вызовов)
NullSpanend()No-op-span (пустой span)
NullMetercreateHistogram(), createUpDownCounter()No-op-измеритель
NullCounter / NullHistogramadd(), record()No-op-инструменты

Выполните composer docs:generate-api-php -- --module=Telemetry, чтобы создать полную таблицу PHPDoc.

Исходный код: examples/33-opentelemetry-observability.php.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Telemetry\OpenTelemetryInterceptor;
$otel = new OpenTelemetryInterceptor(/* optional OTel tracer/meter */);
$span = $otel->startSpan('pdf.render', ['doc.pages' => 12]);
// ... render work ...
$otel->endSpan($span);
$otel->recordMetric('pdf.render.bytes', 482_113, ['profile' => 'pdfa4']);

Когда OTel SDK отсутствует, каждый из приведённых выше вызовов является no-op. Код остаётся тем же, а затраты равны нулю.

Оберните операцию отрисовки и передайте очищенные атрибуты, чтобы метаданные, предоставленные вызывающей стороной, не могли попасть в span.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Telemetry\AttributeSanitizer;
use NextPDF\Telemetry\OpenTelemetryInterceptor;
final readonly class InstrumentedRenderer
{
public function __construct(
private OpenTelemetryInterceptor $otel,
private AttributeSanitizer $sanitizer,
) {}
/**
* @param callable():string $render Returns the rendered PDF bytes.
* @param array<string, mixed> $attributes Caller-supplied span attributes.
*/
public function render(callable $render, array $attributes): string
{
$span = $this->otel->startSpan('pdf.render', $this->sanitizer->sanitize($attributes));
try {
return $render();
} finally {
$this->otel->endSpan($span);
}
}
}
  • No-op-резерв является полным по замыслу. Не защищайте инструментирование вызовом isAvailable() “чтобы сэкономить работу”. No-op уже почти ничего не стоит, а такая защита добавляет именно то ветвление, которое этот подход устраняет.
  • Всегда пропускайте атрибуты, предоставленные вызывающей стороной, через AttributeSanitizer, прежде чем присоединять их к span или метрике. Атрибуты телеметрии — это канал случайной утечки PII.
  • endSpan(null) допустимо: span со значением null — это случай no-op. Сопоставляйте каждый вызов startSpan() с вызовом endSpan() в блоке finally.
  • NullSpanBuilder::setAttribute() возвращает static для цепочки вызовов. В режиме no-op цепочка бездействует по замыслу.
  • Профиль воспроизводимости — structural: в span есть временные метки и идентификаторы трассировки, поэтому два запуска различаются в этих полях.

Когда OTel отсутствует, затраты сводятся к вызову метода no-op и практически равны нулю. Когда OTel присутствует, основные затраты определяет OTel SDK; мост добавляет очистку атрибутов, линейную по их количеству. Значение performance_budget в 1500 ms по времени / 64 MB пиковой памяти — это эталонный бюджет движка, а не соглашение об уровне обслуживания (SLA) телеметрии.

Телеметрия — это поверхность вывода данных. AttributeSanitizer не допускает попадания секретов и PII в span и метрики. Считайте очистку обязательной для любого атрибута, на который влияет вызывающая сторона; это обязательство проекта по безопасной телеметрии. Экспортёр OTel отправляет данные на внешний бэкенд, и этот бэкенд является границей доверия. Настраивайте его конечную точку и учётные данные через менеджер секретов, а не через конфигурацию в репозитории. Исходите из того, что данные span и метрик попадают в приёмник логов, и очищайте их соответствующим образом. См. модель угроз движка в /modules/core/security/.

Этот модуль не делает нормативных заявлений о соответствии спецификации Portable Document Format (PDF). Он связывается с моделью данных OpenTelemetry — внешней спецификацией наблюдаемости, а не пунктом спецификации PDF. No-op-резерв отражает поверхность API OpenTelemetry, чтобы инструментированный код оставался переносимым. Это свойство совместимости API, а не заявление о соответствии PDF. Соответствие движка проверяется с помощью наборов oracle и golden, описанных в /modules/core/conformance/.