Aller au contenu

Observer le rendu avec OpenTelemetry

NextPDF embarque une instrumentation OpenTelemetry intégrée : 10 spans de trace et 7 métriques couvrent tout le cycle de vie de la génération de PDF. Le contrat est clair : zéro surcoût et zéro configuration quand le SDK OTel est absent — aucun échec d’autoload, aucune pénalité de performance. Installe le SDK, enregistre globalement un TracerProvider/MeterProvider, et le même code exporte automatiquement. Un AttributeSanitizer fondé sur une liste d’autorisation applique une politique de données Zero-Trust : la télémétrie ne transporte donc jamais de contenu de document ni de données personnelles.

Cette page est l’équivalent PHP natif du contenu conceptuel OpenTelemetry, indépendamment du transport. Un exemple exécutable et un test de support l’illustrent concrètement.

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

À lui seul, le core te donne une surface d’instrumentation sûre, en mode no-op. Pour exporter des données en direct, ajoute le SDK et un exporter.

Fenêtre de terminal
composer require open-telemetry/sdk:^1
composer require open-telemetry/exporter-otlp:^1 # or zipkin / prometheus

Deux points d’entrée sont disponibles :

  • TelemetryBridge — une façade statique. isAvailable() vérifie la présence d’OTel une seule fois et met le résultat en cache. startSpan() / endSpan() / recordMetric() basculent immédiatement vers un no-op quand OTel est absent. C’est ce qui garantit le contrat de zéro surcoût. Quand OTel est absent, ces appels s’exécutent largement sous la microseconde.
  • OpenTelemetryInterceptor — le chemin intégré au SDK. Il trace automatiquement les 10 spans connus, enregistre les 7 métriques connues et fait passer chaque attribut par AttributeSanitizer. Il vérifie la présence du SDK à l’instanciation et met le résultat en cache. Toutes les références aux classes OTel sont placées derrière des gardes à l’exécution, si bien que la classe se charge même sans le SDK. Les bornes recommandées du BatchSpanProcessor (maxQueueSize=2048, maxExportBatchSize=512) sont exposées sous forme d’accesseurs statiques.

Les 10 spans : document.build, font.resolve, html.parse, writer.serialize, image.decode, layout.pass, barcode.encode, form.build, navigation.build, attachment.embed. Les 7 métriques : render.duration, render.page_count, render.warnings, render.memory_peak, render.file_size, render.font_count, render.image_count.

AttributeSanitizer repose exclusivement sur une liste d’autorisation. Les clés autorisées sont des métadonnées structurelles telles que pdf.page_count, pdf.file_size_bytes, pdf.output_profile et nextpdf.tier. Le HTML brut, les flux d’octets PDF, les blobs base64 et les chemins de fichiers sont supprimés sans condition. Cela concrétise deux points de la directive NIST SP 800-92 : la télémétrie ne doit pas transporter de contenu sensible, et la confidentialité de la télémétrie est un contrôle explicite.

La surface de l’API est générée à partir du PHPDoc de NextPDF\Telemetry\TelemetryBridge, NextPDF\Telemetry\OpenTelemetryInterceptor et NextPDF\Telemetry\AttributeSanitizer. Les principaux membres utilisés ci-dessous sont TelemetryBridge::isAvailable() / startSpan() / endSpan() / recordMetric() ; OpenTelemetryInterceptor::knownSpans() / knownMetrics() / maxQueueSize() / maxExportBatchSize() ; et AttributeSanitizer::sanitize().

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Telemetry\TelemetryBridge;
$span = TelemetryBridge::startSpan('document.build', [
'pdf.page_count' => 1,
'nextpdf.tier' => 'core',
]);
$doc = Document::createStandalone();
$doc->addPage();
$doc->setFont('helvetica', '', 12);
$doc->cell(0, 10, 'Observed render');
$pdf = $doc->getPdfData();
TelemetryBridge::recordMetric('render.file_size', strlen($pdf), []);
TelemetryBridge::endSpan($span); // null-safe when OTel is absent
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/out.pdf');

L’exemple complet démontre le chemin no-op à zéro surcoût : il s’exécute en l’absence du SDK. Il met en pratique la liste d’autorisation du sanitizer et respecte le canal de sortie du harnais. Le harnais de reproductibilité exécute ce script deux fois.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Telemetry\AttributeSanitizer;
use NextPDF\Telemetry\OpenTelemetryInterceptor;
use NextPDF\Telemetry\TelemetryBridge;
// Discover the surface — static, dependency-free, SDK-optional.
$spans = OpenTelemetryInterceptor::knownSpans();
$metrics = OpenTelemetryInterceptor::knownMetrics();
// Zero-Trust Data Policy: the sanitizer drops anything off the allowlist
// and anything that looks like a payload.
$sanitizer = new AttributeSanitizer();
$exported = $sanitizer->sanitize([
'pdf.page_count' => 1,
'pdf.output_profile' => 'PDF/A-4',
'document.raw_html' => '<html><body>secret</body></html>', // dropped
'source.path' => '/var/secret/invoice.pdf', // dropped
]);
$span = TelemetryBridge::startSpan('document.build', [
'pdf.page_count' => 1,
'nextpdf.tier' => 'core',
]);
$doc = Document::createStandalone();
$doc->setTitle('Observability demo');
$doc->addPage();
$doc->setFont('helvetica', 'B', 16);
$doc->cell(0, 12, 'Observe rendering with OpenTelemetry', newLine: true);
$pdf = $doc->getPdfData();
TelemetryBridge::recordMetric('render.page_count', 1, []);
TelemetryBridge::recordMetric('render.file_size', strlen($pdf), []);
TelemetryBridge::endSpan($span);
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/out.pdf');
fwrite(STDERR, sprintf(
"spans=%d metrics=%d otel_available=%s sanitized_keys=%s\n",
count($spans),
count($metrics),
TelemetryBridge::isAvailable() ? 'yes' : 'no',
implode(',', array_keys($exported)),
));
  • La télémétrie ne casse jamais le rendu. Le bridge et l’interceptor absorbent leurs propres erreurs internes. Un exporter mal configuré dégrade l’observabilité, jamais la sortie PDF. N’enveloppe pas le code de rendu dans un catch qui traiterait un échec de télémétrie comme un échec de rendu.
  • endSpan(null) est sûr. startSpan() renvoie null quand OTel est absent, et endSpan() accepte null comme no-op. Appaire-les toujours, et ne fais jamais de branchement sur la valeur de retour.
  • Les métriques nécessitent un MeterProvider enregistré. Si seul un TracerProvider est enregistré, les spans sont exportés, mais les métriques sont silencieusement ignorées. Les métriques sont indicatives. Enregistre les deux fournisseurs pour une couverture complète.
  • Le sanitizer fonctionne uniquement sur liste d’autorisation. Une nouvelle clé d’attribut absente de la liste d’autorisation ne sera pas exportée. Ce comportement est voulu par conception. Étends la liste d’autorisation dans le moteur, et ne contourne pas le sanitizer.
  • Propagation du contexte de trace W3C. La propagation de trace entre services utilise les en-têtes traceparent/tracestate du contexte de trace W3C. Ce sont les propagateurs du SDK OTel qui s’en chargent, et non NextPDF. La recommandation W3C Trace Context n’est pas dans le corpus de vérification ; cette note de propagation reste donc non résolue côté RAG et est présentée comme un conseil d’intégration plutôt que comme une assertion normative. Consulte l’encart latéral.
  • Point d’attention sur la reproductibilité. Le rendu produit un document dont le /ID et la date de modification sont régénérés à chaque sauvegarde (ISO 32000-2 §14.3). Le PDF capturé est comparé avec le profil sémantique, qui couvre uniquement l’AST structurel et les métadonnées.
  • Chemin sans OTel : isAvailable() est mis en cache après le premier appel. Les appels de span et de métrique suivants se réduisent à un seul contrôle booléen, puis retournent. L’exemple instrumenté s’exécute jusqu’au bout avec le SDK absent.
  • Avec OTel : les bornes du BatchSpanProcessor (maxQueueSize=2048, maxExportBatchSize=512) plafonnent la mémoire sous charge soutenue, et l’export reste hors du chemin critique.
  • Le performance_budget (wall_ms: 3000, peak_mb: 128) borne l’exécution du harnais, pas celle de documents arbitraires.
  • Cette recipe couvre l’élément de la liste de lacunes §4.3 pour le #33. Aucun exemple natif PHP n’existait auparavant, seulement la page conceptuelle de style MCP. Un nouveau examples/33-opentelemetry-observability.php ainsi que le tests/Cookbook/Php/ObserveWithOpenTelemetryRecipeTest.php de support ont été écrits.
  • La télémétrie ne doit pas transporter de contenu de document ni de données personnelles. La liste d’autorisation de AttributeSanitizer l’applique dans le code. Le HTML brut, les flux PDF, les blobs base64 et les chemins de fichiers sont supprimés. Cela s’aligne sur la directive NIST SP 800-92, qui vise à garder le contenu sensible hors des journaux et de la télémétrie, et à protéger la confidentialité de la télémétrie.
  • Les attributs que tu ajoutes toi-même restent soumis à la liste d’autorisation. Tu restes responsable de ne pas introduire de valeurs sensibles sous une clé autorisée. Par exemple, ne mets pas d’identifiant utilisateur dans pdf.output_profile.
  • Le détail diagnostique passe par des clés structurées, pas par une charge utile en texte libre. C’est la même discipline que celle que PSR-3 §1.2 applique au contexte des journaux.
ÉnoncéSpécificationClausereference_id
La télémétrie ne doit pas transporter de contenu sensible ; le traitement des données personnelles est obligatoire.NIST SP 800-92§3
La confidentialité de la télémétrie et des journaux est un contrôle explicite.NIST SP 800-92§3
Ce sont des clés de contexte structurées qui portent le détail, pas une charge utile en texte libre.PSR-3§1.2
Le /ID de sortie et les dates se régénèrent à chaque sauvegarde → profil sémantique.ISO 32000-2§14.3

Cette recipe décrit le comportement de l’instrumentation d’ingénierie. Elle n’affirme aucune certification de conformité. Les références à NIST SP 800-92 fondent l’intention de conception « pas de contenu dans la télémétrie », et non une assertion de conformité.