契約 / 可觀測性
可觀測性領域涵蓋一組對外揭露引擎執行期狀態的契約:ContextAwareExceptionInterface 提供結構化的錯誤情境、SpectrumInterface 對應選用的加速 sidecar、JobNotificationInterface 串流回傳工作進度,以及負責處理能力喪失行為的 DegradationPolicy 列舉。
composer require nextpdf/core:^3概念總覽
標題為「概念總覽」的區段ContextAwareExceptionInterface 是診斷用途的契約。每一個 NextPDF 領域例外都會實作它。任何攔截到的 NextPDF 例外都可以轉型為它,以取得結構化情境,供應用程式效能監控(APM)工具、日誌管線或錯誤回報器使用。這份情境是一個關聯式陣列,鍵為 snake_case,值只會是基本型別,也不包含任何巢狀物件。因此,它能穩定序列化為 JSON 或 APM 酬載,不會產生非預期結果。如此一來,就不必再剖析例外訊息來還原診斷資料。自 3.1.0 起,它即為 stable(穩定)。
SpectrumInterface 是選用加速 sidecar 的契約。Spectrum 是一套 CPU 平行運算引擎,會將硬體偵測、PDF 剖析與影像壓縮卸載到本機 sidecar 行程處理。這份契約透過斷路器(circuit breaker)回報可用性,因此即使頻繁執行健康檢查,sidecar 停擺時也不會引發連鎖故障。它會探測硬體能力並快取結果,也會對外揭露目前生效的資源預算,並為上層模組提供通用的請求傳輸(transport)介面。即使沒有 sidecar,引擎仍可運作。這份契約的目的,是讓加速成為可注入的選項,而非硬性相依。JobNotificationInterface 會以產生器(generator)的形式,從 sidecar 的 server-sent-events 端點(endpoint)串流回傳具型別的工作事件。當終止事件抵達,或串流(stream)關閉時,產生器就會停止。
DegradationPolicy 是處理能力喪失時的行為列舉。當某項能力降級時,這套政策會依實際影響,決定要拋出例外、發出警告,或靜默蒐集。當影響屬於法規遵循風險、語意損失或阻斷性時,Strict 會拋出例外。在輸出正確性為強制要求的受規範環境中,這是你應該選用的政策。預設的 Balanced 會針對有界限的降級發出結構化警告並繼續執行,只有在影響屬於阻斷性時才拋出例外。對大多數正式環境部署而言,這是你應該選用的政策。Permissive 會靜默蒐集每一個事件,且永不拋出例外。在可接受盡力而為輸出的預覽或草稿模式中,這是你應該選用的政策。SpectrumInterface、JobNotificationInterface 與 DegradationPolicy 等型別屬於 experimental(實驗性)。它們的相容性承諾比 ContextAwareExceptionInterface 弱。
API 介面
標題為「API 介面」的區段| 型別 | 種類 | 主要成員 | 穩定度 | 起始版本 |
|---|---|---|---|---|
ContextAwareExceptionInterface | 介面(interface) | getContext(): array<string, mixed> | 穩定 | 3.1.0 |
SpectrumInterface | 介面(interface) | isAvailable()、probe()、getBudget()、request() | 實驗性 | 2.1.0 |
JobNotificationInterface | 介面(interface) | streamEvents(string): Generator<int, JobEvent> | 實驗性 | 2.2.0 |
DegradationPolicy | 列舉(enum,字串) | Strict、Balanced、Permissive | 實驗性 | 2.3.0 |
getContext() 只會回傳基本型別,或由基本型別組成的清單。streamEvents() 會持續產出 JobEvent 物件,直到終止事件出現。SpectrumInterface::request() 會以 string 形式回傳原始回應本文。probe() 會回傳一個 HardwareReport,而 getBudget() 會回傳一個 SpectrumBudget。
程式碼範例 — 快速上手
標題為「程式碼範例 — 快速上手」的區段<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\ContextAwareExceptionInterface;use Psr\Log\LoggerInterface;
/** * Log a NextPDF exception with its structured context. * * @param \Throwable $e A caught exception. * @param LoggerInterface $logger A PSR-3 logger. */function logWithContext(\Throwable $e, LoggerInterface $logger): void{ if ($e instanceof ContextAwareExceptionInterface) { $logger->error($e->getMessage(), $e->getContext());
return; }
$logger->error($e->getMessage());}結構化情境會直接傳入日誌紀錄,無須剖析訊息。
程式碼範例 — 正式環境
標題為「程式碼範例 — 正式環境」的區段<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\DegradationPolicy;use NextPDF\Contracts\SpectrumInterface;use Psr\Log\LoggerInterface;
final readonly class AcceleratedParseService{ public function __construct( private ?SpectrumInterface $spectrum, private DegradationPolicy $policy, private LoggerInterface $logger, ) {}
/** * Send a parse batch to the sidecar when healthy, otherwise fall back. * * @param list<array{id: string, data: string}> $documents PDF binaries with caller IDs. * * @return string Raw sidecar response body; decode with a batch-result parser. */ public function parse(array $documents): string { if ($this->spectrum?->isAvailable() === true) { return $this->spectrum->request( 'POST', '/v1/parse', json: ['documents' => $documents], scope: ['parse'], ); }
if ($this->policy === DegradationPolicy::Strict) { throw new \RuntimeException('Accelerator required under strict policy.'); }
$this->logger->info('Accelerator unavailable; using PHP fallback.');
return $this->phpFallback($documents); }
/** @param list<array{id: string, data: string}> $documents @return string */ private function phpFallback(array $documents): string { // Pure-PHP parse path omitted for brevity. return ''; }}可為 null 的 SpectrumInterface 讓加速成為選用功能。這份契約對外揭露單一傳輸方法 request(),會以 string 形式回傳原始回應本文。上層剖析器會把回應本文轉換成 NextPDF\Accelerator\BatchResult。具體的 SpectrumClient 會加上具型別的輔助方法,例如 parseBatch():它會包裝 request(),並直接回傳 BatchResult。那些輔助方法不屬於已凍結的契約。降級政策會決定缺少 sidecar 是否屬於致命情況。
邊界情況與陷阱
標題為「邊界情況與陷阱」的區段- 並非每一個
\Throwable都是 NextPDF 例外。務必先用instanceof ContextAwareExceptionInterface判斷,再呼叫getContext()。 getContext()依契約只會回傳基本型別。若有消費端預期會拿到巢狀物件,就是錯誤假設;這份契約保證回傳的是 JSON 安全的值。SpectrumInterface::isAvailable()受斷路器保護,可以放心頻繁呼叫,但true結果只代表當下時間點的檢查。請處理 sidecar 在檢查與呼叫之間突然失聯的情況。JobNotificationInterface::streamEvents()是一個產生器。反覆走訪它兩次並不會重播事件;它只能被消費一次。DegradationPolicy::Permissive永不拋出例外。在該模式下,會影響法規遵循的降級將會靜默通過。請勿將它用於受規範的輸出。
可觀測性契約增加的成本微乎其微。getContext() 回傳的是預先建好的陣列。isAvailable() 是帶快取、受斷路器保護的健康探測。這份契約要求實作至少將探測結果快取 30 秒,讓熱路徑不會反覆呼叫 sidecar。streamEvents() 的吞吐受限於 sidecar 的事件速率,而非引擎本身。performance_budget 中的 1500 ms wall 與 64 MB 尖峰,是由這些契約所觀測的底層工作決定,而非契約本身。其可重現性設定檔為 structural。事件串流與例外情境都會包含時間戳。兩次執行會在那些欄位上有所不同,但結構會維持完全一致。
安全性注意事項
標題為「安全性注意事項」的區段若結構化例外情境帶有機密資料,它就是資料外洩的攻擊面。這份契約將情境限制為基本型別,可降低物件意外外洩的風險。在情境抵達日誌接收端之前,部署端仍必須清除其中的敏感值。這就是專案日誌政策中的安全遙測義務。加速 sidecar 是透過傳輸層連線的獨立行程。請求方法會攜帶用於授權的範圍宣告(scope claim)。部署端必須把 sidecar 邊界視為信任邊界。設為 Permissive 的降級政策,可能會掩蓋與安全相關的處理能力喪失。在以輸出正確性作為控制措施之處,請使用 Strict。請把例外情境、工作事件與 sidecar 回應都視為可能會被記錄的資料,並據此清除。
符合性
標題為「符合性」的區段本頁未主張任何直接的規範性宣告。可觀測性契約只負責揭露引擎狀態,並未實作任何引擎必須援引其條款的標準化協定。上文提及的安全遙測與日誌清除義務,源自專案內部的日誌政策,而非任何外部標準。當被觀測的操作本身就是標準化操作(例如簽章或 PDF/A 文件)時,其符合性會記載於簽署或擷取頁面。
另請參閱
標題為「另請參閱」的區段- 契約:41 個公開介面(SPI) — SPI 總覽與穩定度分級。
- 可觀測性 — 這些契約揭露的執行期狀態模組。
- 加速器 —
SpectrumInterface背後的 Spectrum sidecar 用戶端。 - 例外 — 實作
ContextAwareExceptionInterface的各種例外。 - 效能 — 被觀測的工作所依循的各項預算。