跳到內容

使用 OpenTelemetry 觀測算繪過程

NextPDF 內建 OpenTelemetry 檢測:橫跨 PDF 產生生命週期的 10 個追蹤 span 與 7 個指標。它的契約是 OTel SDK 不存在時零額外負擔、零設定:不會發生 autoload 失敗,也沒有效能損耗。安裝 SDK 並全域註冊一個 TracerProvider/MeterProvider 後,同一份程式碼就會自動匯出。以允許清單為基礎的 AttributeSanitizer 會強制執行零信任資料政策,因此遙測資料絕不會夾帶文件內容或 PII。

本頁是與傳輸無關的 OpenTelemetry 概念說明的 PHP 原生對應篇。本頁另附一個可執行範例與一支對應測試,會實際演示這個流程。

Terminal window
composer require nextpdf/core:^3

只安裝核心套件,就能提供 no-op 安全的檢測介面。若要即時匯出資料,請再加入 SDK 與一個 exporter。

Terminal window
composer require open-telemetry/sdk:^1
composer 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=2048maxExportBatchSize=512)會以靜態存取子的形式對外公開。

這 10 個 span:document.buildfont.resolvehtml.parsewriter.serializeimage.decodelayout.passbarcode.encodeform.buildnavigation.buildattachment.embed。這 7 個指標:render.durationrender.page_countrender.warningsrender.memory_peakrender.file_sizerender.font_countrender.image_count

AttributeSanitizer 只採用允許清單。獲准的鍵是結構性中繼資料,例如 pdf.page_countpdf.file_size_bytespdf.output_profilenextpdf.tier。原始 HTML、PDF 位元組串流、base64 blob 與檔案系統路徑一律會被丟棄。這落實了 NIST SP 800-92 指引的兩個重點:遙測資料不得夾帶敏感內容,且遙測資料的機密性是一項明確的控制措施。

API 介面是從 NextPDF\Telemetry\TelemetryBridgeNextPDF\Telemetry\OpenTelemetryInterceptorNextPDF\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=2048maxExportBatchSize=512)會在持續負載下限制記憶體用量,且匯出也不會落在熱路徑上。
  • 設定中的 performance_budgetwall_ms: 3000peak_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 參考是用來支撐遙測不夾帶內容的設計意圖,而非符合性宣稱。