Aller au contenu

Télémétrie : pont OpenTelemetry et repli no-op

Le module Télémétrie est le pont OpenTelemetry facultatif du moteur. Lorsque le SDK OpenTelemetry est installé, il émet des spans et des métriques avec des attributs assainis. Lorsqu’il ne l’est pas, un jeu complet d’objets tracer et meter no-op garde les appels d’instrumentation valides et sans coût. Tu peux donc laisser l’instrumentation en place dans les chemins de code sans risque.

Fenêtre de terminal
composer require nextpdf/core:^3

L’objectif de conception est une observabilité à coût nul lorsqu’elle est absente. Les chemins critiques du moteur appellent un tracer et un meter sans condition. Le fait que ces appels produisent quoi que ce soit dépend de l’environnement d’exécution, et non d’un test à chaque point d’appel.

OpenTelemetryInterceptor est le pont. isAvailable() indique si le SDK OTel est présent. startSpan(string $name, array $attributes = []) / endSpan(?object $span) encadrent une opération tracée, et recordMetric() enregistre la valeur d’un compteur ou d’une jauge. Lorsque OTel est absent, l’intercepteur se déclare indisponible et les appels restent inertes. TelemetryBridge raccorde l’intercepteur aux points d’observation du moteur.

AttributeSanitizer est la couche de sécurité. sanitize(array $attributes) nettoie la carte d’attributs avant qu’elle ne quitte le processus. Les attributs de télémétrie sont un canal classique de fuite accidentelle de DCP ; l’assainissement fait donc partie du contrat, ce n’est pas une option ajoutée après coup. Il est @since 2.3.0, tout comme l’intercepteur et le pont.

NullTracer, NullSpanBuilder, NullSpan, NullMeter, NullCounter et NullHistogram constituent le repli no-op. Ils implémentent les mêmes formes que celles exposées par le SDK OTel — spanBuilder(), setAttribute() (chaînable), startSpan(), end(), createHistogram(), createUpDownCounter(), add(), record() — et ne font rien. Parce que le repli est complet, le code instrumenté n’a pas à se ramifier selon la disponibilité ; il appelle le tracer et le no-op absorbe l’appel.

ClasseMembres clésRôle
OpenTelemetryInterceptorisAvailable(), startSpan(), endSpan(), recordMetric()Pont span/metric OTel (@since 2.3.0)
TelemetryBridgecâblage du moteurRaccorde l’intercepteur aux points d’observation (@since 2.3.0)
AttributeSanitizersanitize(array $attributes): arrayAssainisseur d’attributs sans DCP (@since 2.3.0)
NullTracerspanBuilder(string $name): NullSpanBuilderTracer no-op
NullSpanBuildersetAttribute(), startSpan(): NullSpanConstructeur de span no-op (chaînable)
NullSpanend()Span no-op
NullMetercreateHistogram(), createUpDownCounter()Meter no-op
NullCounter / NullHistogramadd(), record()Instruments no-op

Exécute composer docs:generate-api-php -- --module=Telemetry pour obtenir le tableau PHPDoc complet.

Source : 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']);

Lorsque le SDK OTel est absent, chaque appel ci-dessus est un no-op : le code reste identique, le coût est nul.

Encadre un rendu avec des attributs assainis afin que les métadonnées fournies par l’appelant ne puissent pas se retrouver dans un 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);
}
}
}
  • Le repli no-op est complet par conception. Ne protège pas l’instrumentation avec isAvailable() « pour économiser du travail ». Le no-op ne coûte déjà rien, et le test ajoute précisément l’embranchement que la conception vise à supprimer.
  • Fais toujours passer les attributs fournis par l’appelant par AttributeSanitizer avant un span ou une métrique. Les attributs de télémétrie sont un canal de fuite accidentelle de DCP.
  • endSpan(null) est valide — un span nul correspond au cas no-op. Associe chaque startSpan() à un endSpan() dans un finally.
  • NullSpanBuilder::setAttribute() retourne static pour le chaînage ; la chaîne est inerte sous le no-op, ce qui est intentionnel et non un bogue.
  • Le profil de reproductibilité est structural : les spans portent des horodatages et des identifiants de trace, donc deux exécutions diffèrent sur ces champs.

Lorsque OTel est absent, le coût se réduit à un appel de méthode vers un no-op — sans coût notable en pratique. Lorsqu’il est présent, le coût est celui du SDK OTel, et non de ce module ; le pont ajoute l’assainissement des attributs, qui est linéaire en fonction du nombre d’attributs. Le performance_budget de 1500 ms d’horloge / 64 Mo en pic est la référence du moteur, et non un SLA de télémétrie.

La télémétrie est une surface d’exfiltration de données. AttributeSanitizer est le contrôle qui maintient les secrets et les DCP hors des spans et des métriques. Considère l’assainissement comme obligatoire pour tout attribut influencé par l’appelant ; c’est l’exigence du projet pour une télémétrie sûre. L’exportateur OTel envoie les données vers un backend externe. Cette frontière avec le backend est une frontière de confiance. Configure son endpoint et ses identifiants depuis un gestionnaire de secrets, et non depuis une configuration commitée. On doit supposer que les données de span et de métrique atteignent un puits de logs. Nettoie en conséquence. Consulte le modèle de menaces du moteur dans /modules/core/security/.

Ce module ne formule aucune affirmation normative sur la spécification PDF. Il fait le pont avec le modèle de données OpenTelemetry, qui est une spécification d’observabilité externe, et non une clause PDF. Le repli no-op reflète la surface d’API OTel afin que le code instrumenté soit portable ; c’est une propriété de compatibilité d’API, et non une déclaration de conformité PDF. La conformité du moteur est validée par l’oracle et les suites golden décrits dans /modules/core/conformance/.