Skip to content

Observe rendering with OpenTelemetry

NextPDF includes built-in OpenTelemetry instrumentation: 10 trace spans and 7 metrics across the Portable Document Format (PDF) generation lifecycle. The contract is zero overhead and zero configuration when the OTel SDK is absent — no autoload failure, no performance penalty. Install the software development kit (SDK), register a TracerProvider/MeterProvider globally, and the same code exports automatically. An allowlist-based AttributeSanitizer enforces a Zero-Trust Data Policy, so telemetry never carries document content or personally identifiable information (PII).

Use this page as the PHP-native companion to the transport-agnostic OpenTelemetry concepts. A runnable example and backing test cover the flow.

Terminal window
composer require nextpdf/core:^3

Core alone gives you the no-op-safe instrumentation surface. To export live data, add the SDK and one exporter.

Terminal window
composer require open-telemetry/sdk:^1
composer require open-telemetry/exporter-otlp:^1 # or zipkin / prometheus

Use two entry points:

  • TelemetryBridge — a static facade. isAvailable() checks for OTel once and caches the result. startSpan() / endSpan() / recordMetric() short-circuit to a no-op when OTel is absent. This is the zero-overhead contract. When OTel is absent, these calls complete in well under a microsecond.
  • OpenTelemetryInterceptor — the SDK-integrated path. It automatically traces the 10 known spans, records the 7 known metrics, and routes every attribute through AttributeSanitizer. It checks SDK presence at construction and caches the result. All OTel class references sit behind runtime guards, so the class loads even without the SDK. The recommended BatchSpanProcessor bounds (maxQueueSize=2048, maxExportBatchSize=512) are exposed as static accessors.

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

AttributeSanitizer is allowlist-only. It permits structural metadata keys such as pdf.page_count, pdf.file_size_bytes, pdf.output_profile, and nextpdf.tier. It drops raw HTML, PDF byte streams, base64 blobs, and filesystem paths unconditionally. This implements two points from NIST SP 800-92 guidance: telemetry must not carry sensitive content, and telemetry confidentiality is an explicit control.

The application programming interface (API) surface is generated from PHPDoc on NextPDF\Telemetry\TelemetryBridge, NextPDF\Telemetry\OpenTelemetryInterceptor, and NextPDF\Telemetry\AttributeSanitizer. The key members used below are TelemetryBridge::isAvailable() / startSpan() / endSpan() / recordMetric(); OpenTelemetryInterceptor::knownSpans() / knownMetrics() / maxQueueSize() / maxExportBatchSize(); and 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');

The complete example proves the zero-overhead no-op path because it runs with the SDK absent. It exercises the sanitizer allowlist and honors the harness output channel. The reproducibility harness runs this script twice.

<?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)),
));
  • Telemetry never breaks the render. Both the bridge and the interceptor swallow their own internal errors. A misconfigured exporter degrades observability, never the PDF output. Do not wrap render code in a catch that treats a telemetry failure as a render failure.
  • endSpan(null) is safe. startSpan() returns null when OTel is absent, and endSpan() accepts null as a no-op. Always pair them, and never branch on the return value.
  • Metrics need a registered MeterProvider. If only a TracerProvider is registered, spans export but metrics are silently skipped. The metrics are advisory. Register both providers for full coverage.
  • Sanitizer is allowlist-only. A new attribute key that is not in the allowlist will not be exported. This behavior is by design. Extend the allowlist in the engine, and do not bypass the sanitizer.
  • W3C Trace Context propagation. Cross-service trace propagation uses the W3C Trace Context traceparent/tracestate headers. The OTel SDK’s propagators handle them, not NextPDF. The W3C Trace Context Recommendation is not in the verification corpus, so this propagation note is RAG-unresolved and is stated as integration guidance rather than a normative claim. See the sidecar.
  • Reproducibility caveat. The render emits a document whose /ID and modification date are regenerated per save (ISO 32000-2 §14.3). The captured PDF is compared with the semantic profile, which covers only the structural abstract syntax tree (AST) and metadata.
  • No-OTel path: isAvailable() is cached after the first call. Later span and metric calls perform one boolean check, then return. The instrumented example runs to completion with the SDK absent.
  • With OTel: the BatchSpanProcessor bounds (maxQueueSize=2048, maxExportBatchSize=512) cap memory under sustained load, and export stays off the hot path.
  • The performance_budget (wall_ms: 3000, peak_mb: 128) bounds the harness run, not arbitrary documents.
  • This recipe is the §4.3 gap-list coverage for #33. Previously, only the MCP-style concept page existed, with no PHP-native example. A new examples/33-opentelemetry-observability.php plus the backing tests/Cookbook/Php/ObserveWithOpenTelemetryRecipeTest.php were authored.
  • Telemetry must not carry document content or PII. The AttributeSanitizer allowlist enforces this in code. Raw HTML, PDF streams, base64 blobs, and filesystem paths are dropped. This aligns with NIST SP 800-92 guidance on keeping sensitive content out of logs and telemetry, and on protecting telemetry confidentiality.
  • Attributes you add yourself are still subject to the allowlist. You remain responsible for not introducing sensitive values under an allowed key. For example, do not put a user identifier into pdf.output_profile.
  • Structured keys, not free-form payloads, carry diagnostic detail. This is the same discipline that PSR-3 §1.2 applies to log context.
StatementSpecClausereference_id
Telemetry must not carry sensitive content; PII handling is required.NIST SP 800-92§3
Telemetry/log confidentiality is an explicit control.NIST SP 800-92§3
Structured context keys carry detail, not free-form payload.PSR-3§1.2
Output /ID and dates regenerate per save → semantic profile.ISO 32000-2§14.3

This recipe describes engineering instrumentation behavior. It does not assert any compliance certification. The NIST SP 800-92 references ground the no-content-in-telemetry design intent, not a conformance claim.