使用 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 參考是用來支撐遙測不夾帶內容的設計意圖,而非符合性宣稱。