コンテンツにスキップ

契約 / 可観測性

可観測性ドメインには、エンジンの実行時状態を公開する契約が含まれます。構造化されたエラーコンテキストを扱う ContextAwareExceptionInterface、オプションのアクセラレーションサイドカー用の SpectrumInterface、ストリーミングされるジョブ進捗を扱う JobNotificationInterface、そして機能喪失時の挙動を表す DegradationPolicy enum です。

Terminal window
composer require nextpdf/core:^3

ContextAwareExceptionInterface は診断用の契約です。すべての NextPDF ドメイン例外がこれを実装します。キャッチした任意の NextPDF 例外をこれにキャストすると、アプリケーションパフォーマンスモニタリング(APM)ツール、ロギングパイプライン、またはエラーレポーター向けの構造化されたコンテキストを取得できます。このコンテキストは、snake_case のキーとプリミティブ値のみを持つ連想配列です。ネストされたオブジェクトは保持しません。そのため、JSON または APM ペイロードへ問題なくシリアライズされます。これにより、診断データを取り出すために例外メッセージをパースする必要がなくなります。3.1.0 以降は stable です。

SpectrumInterface は、オプションのアクセラレーションサイドカー用の契約です。Spectrum は、ハードウェア検出、PDF パース、画像圧縮をローカルのサイドカープロセスにオフロードする CPU 並列エンジンです。この契約はサーキットブレーカーの背後で可用性を報告するため、サイドカーがダウンしているときに頻繁なヘルスチェックが障害を連鎖させることはありません。キャッシュされた結果を使ってハードウェア機能をプローブします。アクティブなリソースバジェットを公開します。上位レベルのモジュール向けに汎用的なリクエストトランスポートを提供します。エンジンはサイドカーなしでも動作します。この契約は、アクセラレーションを必須の依存関係ではなく、注入可能なオプションとして扱うために存在します。JobNotificationInterface は、サイドカーの server-sent-events エンドポイントから型付きのジョブイベントをジェネレーターとしてストリーミングします。ジェネレーターは、終端イベントが到着するか、ストリームが閉じると終了します。

DegradationPolicy は、機能喪失時の挙動を表す enum です。機能が低下したとき、このポリシーは例外をスローするか、警告するか、または静かに収集するかを影響度に基づいて判断します。Strict は、影響がコンプライアンスリスク、意味的損失、またはブロッキングである場合に例外をスローします。出力の正確性が必須となる規制環境向けの選択肢です。デフォルトの Balanced は、限定的な機能低下に対しては構造化された警告を発して処理を続行し、ブロッキングの影響がある場合にのみ例外をスローします。ほとんどの本番デプロイメント向けの選択肢です。Permissive は、すべてのイベントを静かに収集し、例外を一切スローしません。ベストエフォートの出力が許容されるプレビューまたはドラフトモード向けの選択肢です。SpectrumInterfaceJobNotificationInterface、および DegradationPolicy 型は experimental です。これらの互換性保証は ContextAwareExceptionInterface よりも弱いものです。

種別主なメンバー安定性導入バージョン
ContextAwareExceptionInterfaceinterfacegetContext(): array<string, mixed>stable3.1.0
SpectrumInterfaceinterfaceisAvailable()probe()getBudget()request()experimental2.1.0
JobNotificationInterfaceinterfacestreamEvents(string): Generator<int, JobEvent>experimental2.2.0
DegradationPolicyenum(string)StrictBalancedPermissiveexperimental2.3.0

getContext() は、プリミティブまたはプリミティブのリストのみを返します。streamEvents() は、終端イベントが発生するまで JobEvent オブジェクトを yield します。SpectrumInterface::request() は、生のレスポンスボディを string として返します。probe()HardwareReport を返し、getBudget()SpectrumBudget を返します。

examples/contracts/observability-quickstart.php
<?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());
}

構造化されたコンテキストは、メッセージをパースせずにログレコードへ渡されます。

examples/contracts/observability-production.php
<?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 '';
}
}

nullable な SpectrumInterface により、アクセラレーションはオプションになります。この契約が公開する単一のトランスポートメソッド request() は、生のレスポンスボディを string として返します。上位レベルのパーサーがそのボディを NextPDF\Accelerator\BatchResult に変換します。具象クラスの SpectrumClient は、parseBatch() などの型付きヘルパーを追加します。これらは request() をラップし、BatchResult を直接返します。これらのヘルパーは、凍結された契約の一部ではありません。デグラデーションポリシーは、サイドカーの欠如を致命的とするかどうかを決定します。

  • すべての \Throwable が NextPDF 例外であるとは限りません。必ず instanceof ContextAwareExceptionInterface でガードしてから、getContext() を呼び出してください。
  • getContext() は、契約上プリミティブのみを返します。ネストされたオブジェクトを期待する利用側は、誤った想定をしています。契約は JSON セーフな値を保証します。
  • SpectrumInterface::isAvailable() はサーキットブレーカーの背後にあり、頻繁に呼び出しても安全ですが、true という結果はある時点でのチェックにすぎません。チェックと呼び出しの間にサイドカーが切断されるケースに対処してください。
  • JobNotificationInterface::streamEvents() はジェネレーターです。2 回反復しても、イベントは再生されません。一度だけ消費してください。
  • DegradationPolicy::Permissive は例外を一切スローしません。このモードでは、コンプライアンスに影響する機能低下が黙って通過します。規制対象の出力には使用しないでください。

可観測性の契約が追加するコストはごくわずかです。getContext() は、事前に構築された配列を返します。isAvailable() は、キャッシュされたサーキットブレーカー付きのヘルスプローブです。この契約は、実装にプローブ結果を少なくとも 30 秒間キャッシュすることを要求するため、ホットパスがサイドカーを繰り返し呼び出すことはありません。streamEvents() は、エンジンではなくサイドカーのイベントレートによって制約されます。ウォール 1500 ms およびピーク 64 MB という performance_budget は、契約そのものではなく、契約が観測する基盤処理によって決まります。再現性プロファイルは structural です。イベントストリームと例外コンテキストには、タイムスタンプが含まれます。2 回の実行ではこれらのフィールドが異なりますが、構造は同一のままです。

構造化された例外コンテキストは、シークレットを含む場合、データ漏えいの経路になります。この契約はコンテキストをプリミティブに制限するため、オブジェクトの偶発的な漏えいを抑えます。それでもデプロイ側は、コンテキストがログシンクに到達する前に機密値をスクラブする必要があります。これは、プロジェクトのロギングポリシーにおける安全なテレメトリーの義務です。アクセラレーションサイドカーは、トランスポート経由で到達する別個のプロセスです。request メソッドは、認可のためのスコープクレームを伝達します。デプロイ側は、サイドカーの境界を信頼境界として扱う必要があります。Permissive に設定されたデグラデーションポリシーは、セキュリティに関わる機能喪失を覆い隠すおそれがあります。出力の正確性が管理項目となる場合は Strict を使用してください。例外コンテキスト、ジョブイベント、サイドカーのレスポンスは、ログに記録される可能性のあるデータとして扱い、それに応じてスクラブしてください。

このページは、直接的な規範的主張を行いません。可観測性の契約はエンジンの状態を公開するものであり、エンジンが条項を引用しなければならないような標準化されたプロトコルを実装するものではありません。上記で参照した安全なテレメトリーおよびログスクラブの義務は、外部標準ではなく、プロジェクトの内部ロギングポリシーに由来します。観測対象の操作自体が標準化されている場合 — 署名や PDF/A ドキュメントなど — その準拠は署名または抽出のページに記載されます。