Przejdź do głównej zawartości

Telemetria: most OpenTelemetry i awaryjna implementacja no-op

Moduł Telemetry to opcjonalny most łączący silnik z OpenTelemetry (OTel) SDK. Gdy SDK jest zainstalowany, emituje spany i metryki z atrybutami po sanityzacji. Gdy SDK jest nieobecny, kompletny zestaw obiektów no-op dla tracera i miernika sprawia, że wywołania instrumentacji pozostają poprawne i praktycznie bezkosztowe. Instrumentację można bezpiecznie pozostawić w ścieżce kodu.

Okno terminala
composer require nextpdf/core:^3

Celem projektu jest obserwowalność przy zerowym koszcie, gdy SDK jest nieobecny. Gorące ścieżki silnika wywołują tracer i miernik bez wcześniejszych sprawdzeń. To, czy te wywołania wykonują pracę, zależy od środowiska uruchomieniowego, a nie od warunku w każdym miejscu wywołania.

OpenTelemetryInterceptor pełni rolę mostu. isAvailable() informuje, czy SDK OTel jest obecny. startSpan(string $name, array $attributes = []) / endSpan(?object $span) obejmują operację śledzeniem, a recordMetric() zapisuje wartość licznika lub wskaźnika. Gdy OTel jest nieobecny, interceptor informuje o niedostępności, a wywołania pozostają bezczynne. TelemetryBridge łączy interceptor z punktami obserwacji silnika.

AttributeSanitizer jest warstwą bezpieczeństwa. sanitize(array $attributes) oczyszcza mapę atrybutów, zanim opuści proces. Atrybuty telemetrii to częsty kanał przypadkowego ujawnienia informacji umożliwiających identyfikację osoby (PII), dlatego sanityzacja jest częścią kontraktu, a nie dodatkiem. Sanitizer, interceptor i most są @since 2.3.0.

NullTracer, NullSpanBuilder, NullSpan, NullMeter, NullCounter i NullHistogram stanowią awaryjną implementację no-op. Odwzorowują kształt wywołań udostępnianych przez SDK OTel: spanBuilder(), setAttribute() (łańcuchowe), startSpan(), end(), createHistogram(), createUpDownCounter(), add() i record(). Nie wykonują żadnej pracy. Ponieważ awaryjna implementacja jest kompletna, instrumentowany kod nie rozgałęzia się w zależności od dostępności; wywołuje tracer, a no-op przejmuje wywołanie bez efektu.

KlasaKluczowe składoweRola
OpenTelemetryInterceptorisAvailable(), startSpan(), endSpan(), recordMetric()Most spanów i metryk OTel (@since 2.3.0)
TelemetryBridgepołączenia z silnikiemŁączy interceptor z punktami obserwacji silnika (@since 2.3.0)
AttributeSanitizersanitize(array $attributes): arrayOczyszcza atrybuty z uwzględnieniem bezpieczeństwa PII (@since 2.3.0)
NullTracerspanBuilder(string $name): NullSpanBuilderTracer no-op
NullSpanBuildersetAttribute(), startSpan(): NullSpanKonstruktor spanów no-op (łańcuchowy)
NullSpanend()Span no-op
NullMetercreateHistogram(), createUpDownCounter()Miernik no-op
NullCounter / NullHistogramadd(), record()Instrumenty no-op

Uruchom composer docs:generate-api-php -- --module=Telemetry, aby wygenerować pełną tabelę PHPDoc.

Źródło: 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']);

Gdy SDK OTel jest nieobecny, każde z powyższych wywołań jest no-op. Kod pozostaje identyczny, a koszt wynosi zero.

Opakuj operację renderowania, używając atrybutów po sanityzacji, aby metadane dostarczone przez wywołującego nie mogły wyciekać do spanu.

<?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);
}
}
}
  • Awaryjna implementacja no-op jest z założenia kompletna. Nie osłaniaj instrumentacji wywołaniem isAvailable() „dla oszczędności pracy”. No-op i tak nic nie kosztuje, a taka osłona dodaje rozgałęzienie, które ten projekt eliminuje.
  • Zawsze przepuszczaj atrybuty dostarczone przez wywołującego przez AttributeSanitizer, zanim dołączysz je do spanu lub metryki. Atrybuty telemetrii to przypadkowy kanał ujawniania PII.
  • endSpan(null) jest poprawne: span o wartości null to przypadek no-op. Każde startSpan() paruj z endSpan() w bloku finally.
  • NullSpanBuilder::setAttribute() zwraca static na potrzeby łańcuchowania. W trybie no-op łańcuch jest z założenia bezczynny.
  • Profil odtwarzalności jest structural: spany zawierają znaczniki czasu i identyfikatory śladu, dlatego dwa uruchomienia różnią się w tych polach.

Gdy OTel jest nieobecny, koszt ogranicza się do wywołania metody trafiającego do no-op i jest praktycznie zerowy. Gdy OTel jest obecny, koszt pochodzi z SDK OTel; most dodaje sanityzację atrybutów, która jest liniowa względem liczby atrybutów. Wartość performance_budget wynosząca 1500 ms czasu zegarowego / 64 MB wartości szczytowej to referencyjny budżet silnika, a nie gwarancja poziomu usług (SLA) dla telemetrii.

Telemetria jest powierzchnią wyprowadzania danych. AttributeSanitizer utrzymuje sekrety i PII poza spanami i metrykami. Traktuj sanityzację jako obowiązkową dla każdego atrybutu, na który wpływa wywołujący; w tym projekcie jest to wymóg bezpiecznej telemetrii. Eksporter OTel wysyła dane do zewnętrznego zaplecza, a to zaplecze stanowi granicę zaufania. Konfiguruj jego punkt końcowy i poświadczenia z menedżera sekretów, a nie z konfiguracji zapisanej w repozytorium. Załóż, że dane spanów i metryk trafiają do odbiornika logów, i odpowiednio je oczyszczaj. Zobacz model zagrożeń silnika w /modules/core/security/.

Ten moduł nie formułuje żadnego normatywnego twierdzenia dotyczącego specyfikacji formatu Portable Document Format (PDF). Stanowi most do modelu danych OpenTelemetry, zewnętrznej specyfikacji obserwowalności, a nie do klauzuli PDF. Awaryjna implementacja no-op odwzorowuje powierzchnię API OpenTelemetry, aby instrumentowany kod pozostał przenośny. To właściwość zgodności API, a nie deklaracja zgodności z PDF. Zgodność silnika jest walidowana przez wyrocznie i zestawy wzorcowe opisane w /modules/core/conformance/.