Observe rendering with OpenTelemetry
At a glance
Section titled “At a glance”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.
Install
Section titled “Install”composer require nextpdf/core:^3Core alone gives you the no-op-safe instrumentation surface. To export live data, add the SDK and one exporter.
composer require open-telemetry/sdk:^1composer require open-telemetry/exporter-otlp:^1 # or zipkin / prometheusConceptual overview
Section titled “Conceptual overview”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 throughAttributeSanitizer. 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 recommendedBatchSpanProcessorbounds (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.
API surface
Section titled “API surface”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().
Code sample — Quick start
Section titled “Code sample — Quick start”<?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');Code sample — Production
Section titled “Code sample — Production”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)),));Edge cases & gotchas
Section titled “Edge cases & gotchas”- 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()returnsnullwhen OTel is absent, andendSpan()acceptsnullas a no-op. Always pair them, and never branch on the return value.- Metrics need a registered
MeterProvider. If only aTracerProvideris 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/tracestateheaders. 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
/IDand 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.
Performance
Section titled “Performance”- 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
BatchSpanProcessorbounds (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.phpplus the backingtests/Cookbook/Php/ObserveWithOpenTelemetryRecipeTest.phpwere authored.
Security notes
Section titled “Security notes”- Telemetry must not carry document content or PII. The
AttributeSanitizerallowlist 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.
Conformance
Section titled “Conformance”| Statement | Spec | Clause | reference_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.