契约 / 可观测性
可观测性领域涵盖一组向外部暴露引擎运行时状态的契约: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 的事件速率,而非引擎本身。1500 ms wall 与 64 MB 峰值的 performance_budget,是由这些契约所观测的底层工作决定,而非契约本身。其可重现性配置文件为 structural。事件流与异常上下文都会包含时间戳。两次执行会在那些字段上有所不同,但结构保持完全一致。
安全性注意事项
标题为“安全性注意事项”的章节若结构化异常上下文带有机密数据,它就是一个数据泄露攻击面。这份契约把上下文限制为基本类型,可降低对象意外外泄的风险。在上下文抵达日志接收端之前,部署端仍必须清除其中的敏感值。这就是项目日志政策中的安全遥测义务。加速 sidecar 是一个通过传输层连接的独立进程。请求方法会携带用于授权的范围声明(scope claim)。部署端必须把 sidecar 边界视为信任边界。设为 Permissive 的降级政策,可能会掩盖与安全相关的处理能力丧失。在以输出正确性作为控制措施的场景中,请使用 Strict。请把异常上下文、工作事件与 sidecar 响应都视为可能会被记录的数据,并据此加以清除。
符合性
标题为“符合性”的章节本页未主张任何直接的规范性声明。可观测性契约只负责暴露引擎状态,并未实现任何引擎必须援引其条款的标准化协议。上文提及的安全遥测与日志清除义务,源自项目内部的日志政策,而非任何外部标准。当被观测的操作本身就是标准化的(例如签章或 PDF/A 文件),其符合性会记载于签署或提取页面上。
另请参阅
标题为“另请参阅”的章节- 契约:41 个公开接口(SPI) — SPI 总览与稳定度分级。
- 可观测性 — 这些契约所暴露的运行时状态模块。
- 加速器 —
SpectrumInterface背后的 Spectrum sidecar 客户端。 - 异常 — 实现
ContextAwareExceptionInterface的各种异常。 - 性能 — 被观测的工作所遵循的各项预算。