以 OpenTelemetry 观测渲染过程
NextPDF 内置 OpenTelemetry 插桩:覆盖 PDF 生成生命周期中的 10 个追踪 span 和 7 个指标。它的契约是 OTel SDK 不存在时零开销、零配置:不会发生 autoload 失败,也不会产生性能损耗。安装 SDK,并全局注册一个 TracerProvider/MeterProvider 后,同一份代码就会自动导出数据。基于允许清单的 AttributeSanitizer 会强制执行零信任数据策略,因此遥测数据绝不会包含文档内容或 PII。
本页是与传输无关的 OpenTelemetry 概念说明在 PHP 原生实现中的对应篇。页面包含一个可执行示例,并有一项对应测试会实际运行它。
composer require nextpdf/core:^3仅安装核心包即可获得 no-op 安全的插桩接口。若要实时导出数据,再安装 SDK 和一个 exporter。
composer require open-telemetry/sdk:^1composer require open-telemetry/exporter-otlp:^1 # or zipkin / prometheus概念概览
标题为“概念概览”的章节有两个入口点:
TelemetryBridge:一个静态 facade(门面)。isAvailable()会检查一次 OTel 是否存在,并缓存结果。OTel 不存在时,startSpan()/endSpan()/recordMetric()会短路为 no-op。这就是零开销契约。OTel 不存在时,这些调用会在远低于一微秒内完成。OpenTelemetryInterceptor:接入 SDK 的路径。它会自动追踪这 10 个已知 span、记录这 7 个已知指标,并将每个属性都传入AttributeSanitizer。它会在构造时检查 SDK 是否存在,并缓存结果。所有 OTel 类的引用都放在运行时防护之后,因此即使没有 SDK,这个类仍可加载。建议的BatchSpanProcessor边界值(maxQueueSize=2048、maxExportBatchSize=512)会通过静态访问器对外公开。
这 10 个 span 分别是:document.build、font.resolve、html.parse、writer.serialize、image.decode、layout.pass、barcode.encode、form.build、navigation.build、attachment.embed。这 7 个指标分别是:render.duration、render.page_count、render.warnings、render.memory_peak、render.file_size、render.font_count、render.image_count。
AttributeSanitizer 只接受允许清单中的键。允许的键是结构化元数据,例如 pdf.page_count、pdf.file_size_bytes、pdf.output_profile 和 nextpdf.tier。原始 HTML、PDF 字节流、base64 blob 和文件系统路径一律会被丢弃。这实现了 NIST SP 800-92 指引的两个重点:遥测数据不得包含敏感内容,并且遥测数据的机密性必须作为明确控制措施处理。
API 接口
标题为“API 接口”的章节API 接口根据 NextPDF\Telemetry\TelemetryBridge、NextPDF\Telemetry\OpenTelemetryInterceptor 和 NextPDF\Telemetry\AttributeSanitizer 的 PHPDoc 生成。下面使用的主要成员是 TelemetryBridge::isAvailable() / startSpan() / endSpan() / recordMetric();OpenTelemetryInterceptor::knownSpans() / knownMetrics() / maxQueueSize() / maxExportBatchSize();以及 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');代码示例:生产环境
标题为“代码示例:生产环境”的章节这个完整示例会验证零开销的 no-op 路径,因为它会在 SDK 不存在的情况下执行。它会演示 sanitizer 的允许清单,并遵守测试工具的输出通道。可复现性测试工具会把这支脚本运行两次。
<?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)),));边界情况与陷阱
标题为“边界情况与陷阱”的章节- 遥测绝不会让渲染中断。 bridge 和 interceptor 都会吞掉自身的内部错误。配置有误的 exporter 只会降低可观测性,绝不会影响 PDF 输出。不要把渲染代码包在会将遥测失败视为渲染失败的 catch 中。
endSpan(null)是安全的。startSpan()在 OTel 不存在时会返回null,而endSpan()会把null当成 no-op 接受。务必成对使用它们,也绝不要根据返回值做分支判断。- 指标需要注册一个
MeterProvider。 如果只注册了TracerProvider,span 会导出,但指标会被静默跳过。这些指标属于建议性质。只有同时注册两个 provider,才能获得完整覆盖范围。 - sanitizer 只采用允许清单。 不在允许清单上的新属性键不会被导出。这个行为是刻意设计的。请在引擎中扩展允许清单,不要绕过 sanitizer。
- W3C Trace Context 传播。 跨服务的追踪传播使用 W3C Trace Context 的
traceparent/tracestate头。处理它们的是 OTel SDK 的传播器,而不是 NextPDF。W3C Trace Context 建议文档不在验证语料库中,因此这项传播说明尚未经 RAG 确认,仅作为集成指引陈述,并非规范性宣称。请参见补充说明。 - 可复现性注意事项。 此渲染输出文件的
/ID和修改日期会在每次保存时重新生成(ISO 32000-2 §14.3)。捕获到的 PDF 会以 semantic 配置文件比对,该配置文件只覆盖结构化 AST 和元数据。
- 无 OTel 路径:
isAvailable()会在第一次调用后被缓存。后续的 span 和指标调用只进行一次布尔检查,然后直接返回。这个经过插桩的示例在 SDK 不存在时仍会执行到结束。 - 使用 OTel 时:
BatchSpanProcessor边界值(maxQueueSize=2048、maxExportBatchSize=512)会在持续负载下限制内存用量,而汇出也不会落在热路径上。 - 配置中的
performance_budget(wall_ms: 3000、peak_mb: 128)限制的是测试工具本次执行,而非任意文件。 - 这个 recipe(示例)用于覆盖 #33 的 §4.3 缺口清单。此前没有 PHP 原生示例,只有 MCP 风格的概念页面。我们编写了新的
examples/33-opentelemetry-observability.php,以及对应的tests/Cookbook/Php/ObserveWithOpenTelemetryRecipeTest.php。
安全注意事项
标题为“安全注意事项”的章节- 遥测不得夹带文件内容或 PII。
AttributeSanitizer允许清单会在代码层面强制执行这一点。原始 HTML、PDF 流、base64 blob 和文件系统路径都会被丢弃。这与 NIST SP 800-92 中让敏感内容远离日志和遥测,并保护遥测机密性的指引一致。 - 你自己加入的属性同样受允许清单约束。你仍须负责避免在允许的键下面放入敏感的值。例如,不要把用户标识符放进
pdf.output_profile。 - 诊断细节应由结构化键承载,而不是自由形式的负载。这与 PSR-3 §1.2 对日志上下文数据的约束一致。
符合性
标题为“符合性”的章节| 陈述 | 规范 | 条款 | 参考 ID |
|---|---|---|---|
| 遥测不得包含敏感内容;必须处理 PII。 | NIST SP 800-92 | §3 | |
| 遥测/日志的机密性是一项明确的控制措施。 | NIST SP 800-92 | §3 | |
| 细节由结构化上下文键承载,而非自由形式的负载。 | PSR-3 | §1.2 | |
输出的 /ID 和日期会在每次保存时重新生成 → semantic 配置文件。 | ISO 32000-2 | §14.3 |
这个 recipe 描述的是工程插桩行为。它并未主张任何符合性认证。上述 NIST SP 800-92 参考用于支撑遥测不包含文档内容的设计意图,而非符合性宣称。