跳到內容

Telemetry:OpenTelemetry 橋接與無操作備援

Telemetry 模組是引擎可選用的 OpenTelemetry 橋接層。安裝 OpenTelemetry SDK 後,模組會發出帶有已清理屬性的 span 與 metric。若未安裝,完整的無操作 tracer 與 meter 物件會讓檢測呼叫持續有效,且不產生成本。因此,你永遠可以安全地把檢測程式碼留在執行路徑中。

Terminal window
composer require nextpdf/core:^3

設計目標是達成「不存在時零成本」的可觀測性。引擎的熱路徑會無條件呼叫 tracer 與 meter。這些呼叫是否真的執行工作,取決於執行階段,而非每個呼叫點上的條件判斷。

OpenTelemetryInterceptor 就是這層橋接。isAvailable() 會回報 OTel SDK 是否存在。startSpan(string $name, array $attributes = []) / endSpan(?object $span) 會包住一次受追蹤的操作,而 recordMetric() 會記錄計數器或量測值。當 OTel 不存在時,攔截器會回報不可用,這些呼叫也會是惰性的(不執行任何動作)。TelemetryBridge 會把攔截器接到引擎的觀測點上。

AttributeSanitizer 是安全防護層。sanitize(array $attributes) 會在屬性對映離開行程前先清理。遙測屬性是典型的意外 PII 外洩管道,因此屬性清理是合約的一部分,而非附加選項。它、攔截器與橋接層同樣都是 @since 2.3.0

NullTracerNullSpanBuilderNullSpanNullMeterNullCounterNullHistogram 是無操作備援。它們實作了與 OTel SDK 所揭露的 API 形狀一致——spanBuilder()setAttribute()(可鏈式呼叫)、startSpan()end()createHistogram()createUpDownCounter()add()record()——但不執行任何動作。由於備援是完整的,受檢測的程式碼不需依可用性分支;它直接呼叫 tracer,而無操作實作會吸收這次呼叫。

類別主要成員角色
OpenTelemetryInterceptorisAvailable(), startSpan(), endSpan(), recordMetric()OTel span/metric 橋接(@since 2.3.0
TelemetryBridge引擎接線將攔截器連接到觀測點(@since 2.3.0
AttributeSanitizersanitize(array $attributes): arrayPII 安全的屬性清理器(@since 2.3.0
NullTracerspanBuilder(string $name): NullSpanBuilder無操作 tracer
NullSpanBuildersetAttribute(), startSpan(): NullSpan無操作 span builder(可鏈式呼叫)
NullSpanend()無操作 span
NullMetercreateHistogram(), createUpDownCounter()無操作 meter
NullCounter / NullHistogramadd(), record()無操作儀器

請執行 composer docs:generate-api-php -- --module=Telemetry 取得完整的 PHPDoc 表格。

來源:examples/33-opentelemetry-observability.php

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Telemetry\OpenTelemetryInterceptor;
$otel = new OpenTelemetryInterceptor(/* optional OTel tracer/meter */);
$span = $otel->startSpan('pdf.render', ['doc.pages' => 12]);
// ... render work ...
$otel->endSpan($span);
$otel->recordMetric('pdf.render.bytes', 482_113, ['profile' => 'pdfa4']);

在 OTel SDK 不存在時,上面每一次呼叫都是無操作——程式碼完全相同,成本為零。

用已清理的屬性包覆單次渲染,確保呼叫端提供的中繼資料無法洩漏到 span 中。

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Telemetry\AttributeSanitizer;
use NextPDF\Telemetry\OpenTelemetryInterceptor;
final readonly class InstrumentedRenderer
{
public function __construct(
private OpenTelemetryInterceptor $otel,
private AttributeSanitizer $sanitizer,
) {}
/**
* @param callable():string $render Returns the rendered PDF bytes.
* @param array<string, mixed> $attributes Caller-supplied span attributes.
*/
public function render(callable $render, array $attributes): string
{
$span = $this->otel->startSpan('pdf.render', $this->sanitizer->sanitize($attributes));
try {
return $render();
} finally {
$this->otel->endSpan($span);
}
}
}
  • 無操作備援在設計上就是完整。不要為了「省下工作」而用 isAvailable() 來防護檢測。無操作原本就是零成本;這道防護會新增一個分支,而此設計的目的正是消除這個分支。
  • 務必先讓呼叫端提供的屬性通過 AttributeSanitizer,再進入 span 或 metric。遙測屬性是 PII 意外外洩的管道。
  • endSpan(null) 是有效的——null span 代表無操作情境。每一次 startSpan() 都要在 finally 中搭配一次 endSpan()
  • NullSpanBuilder::setAttribute() 會回傳 static 以供鏈式呼叫;在無操作下,這條鏈是惰性的,這是刻意而為,並非錯誤。
  • 可重現性設定檔為 structural:span 會帶有時間戳記與 trace id,因此兩次執行在這些欄位上會有所不同。

當 OTel 不存在時,成本只是一個無操作方法呼叫——實務上等於零成本。當 OTel 存在時,成本來自 OTel SDK,而非本模組;橋接層額外增加了屬性清理,其成本與屬性數量成線性關係。1500 ms 牆鐘時間/64 MB 峰值的 performance_budget 是引擎的參考值,而非遙測的 SLA。

遙測是一個資料外送面。AttributeSanitizer 是防止機密與 PII 進入 span 與 metric 的控制機制。任何受呼叫端影響的屬性,都應將清理視為必要;這是本專案的安全遙測義務。OTel 匯出器會把資料送往外部後端。這個後端邊界就是一道信任邊界。端點與憑證請來自機密管理器,而不是已提交(committed)的設定檔。你應假設 span 與 metric 資料會抵達某個日誌接收端。請據此清理。請參閱 /modules/core/security/ 中的引擎威脅模型。

本模組不提出任何 PDF 規範的規範性聲明。它橋接的是 OpenTelemetry 資料模型;那是外部的可觀測性規範,而不是 PDF 條款。無操作備援會鏡映 OTel API 介面,讓受檢測的程式碼具備可攜性;這是 API 相容性特性,而非 PDF 符合性聲明。引擎符合性是透過 /modules/core/conformance/ 中所述的 oracle 與 golden 測試套件驗證。