Zum Inhalt springen

Contracts / Observability

Die Observability-Domäne bündelt die Contracts, die den Laufzeitzustand der Engine offenlegen: ContextAwareExceptionInterface für strukturierten Fehlerkontext, SpectrumInterface für den optionalen Beschleunigungs-Sidecar, JobNotificationInterface für gestreamten Job-Fortschritt und das DegradationPolicy-Enum für das Verhalten bei Capability-Verlust.

Terminal-Fenster
composer require nextpdf/core:^3

ContextAwareExceptionInterface ist der Diagnose-Contract. Jede Domänen-Exception von NextPDF implementiert ihn. Jede abgefangene NextPDF-Exception kann auf diesen Contract gecastet werden, um strukturierten Kontext für ein Application-Performance-Monitoring-Tool (APM), eine Logging-Pipeline oder einen Error-Reporter abzurufen. Der Kontext ist ein assoziatives Array mit snake_case-Schlüsseln und ausschließlich primitiven Werten. Er enthält keine verschachtelten Objekte. Dadurch lässt er sich zuverlässig als JSON- oder APM-Payload serialisieren. Das macht es überflüssig, eine Exception-Meldung zu parsen, um Diagnosedaten zurückzugewinnen. Er ist seit 3.1.0 stable.

SpectrumInterface ist der Contract für den optionalen Beschleunigungs-Sidecar. Spectrum ist eine CPU-parallele Engine, die Hardware-Erkennung, PDF-Parsing und Bildkomprimierung in einen lokalen Sidecar-Prozess auslagert. Der Contract meldet die Verfügbarkeit hinter einem Circuit-Breaker, sodass ein häufiger Health-Check keine Fehlerkaskade auslöst, wenn der Sidecar ausgefallen ist. Er prüft Hardware-Fähigkeiten anhand eines zwischengespeicherten Ergebnisses. Er legt das aktive Resource-Budget offen. Er stellt einen allgemeinen Request-Transport für höhere Module bereit. Die Engine funktioniert auch ohne den Sidecar. Der Contract existiert, damit die Beschleunigung eine injizierbare Option bleibt und keine harte Abhängigkeit wird. JobNotificationInterface streamt typisierte Job-Events vom Server-Sent-Events-Endpunkt des Sidecars als Generator. Der Generator endet, sobald ein terminales Event eintrifft oder der Stream schließt.

DegradationPolicy ist das Verhaltens-Enum für Capability-Verlust. Wenn eine Capability degradiert wird, entscheidet die Policy auswirkungsabhängig, ob sie eine Exception wirft, Warnungen ausgibt oder Events still sammelt. Strict wirft, wenn die Auswirkung ein Compliance-Risiko, ein semantischer Verlust oder blockierend ist. Es ist die Wahl für regulierte Umgebungen, in denen die Korrektheit der Ausgabe zwingend ist. Balanced, die Voreinstellung, gibt strukturierte Warnungen aus, fährt bei begrenzter Degradation fort und wirft nur bei blockierender Auswirkung. Es ist die Wahl für die meisten Produktiv-Deployments. Permissive sammelt jedes Event still und wirft nie. Es ist die Wahl für einen Vorschau- oder Entwurfsmodus, in dem eine Best-Effort-Ausgabe akzeptabel ist. Die Typen SpectrumInterface, JobNotificationInterface und DegradationPolicy sind experimental. Ihr Kompatibilitätsversprechen ist schwächer als das von ContextAwareExceptionInterface.

TypArtWichtige MitgliederStabilitätSeit
ContextAwareExceptionInterfaceinterfacegetContext(): array<string, mixed>stable3.1.0
SpectrumInterfaceinterfaceisAvailable(), probe(), getBudget(), request()experimental2.1.0
JobNotificationInterfaceinterfacestreamEvents(string): Generator<int, JobEvent>experimental2.2.0
DegradationPolicyenum (string)Strict, Balanced, Permissiveexperimental2.3.0

getContext() gibt ausschließlich Primitive oder Listen von Primitiven zurück. streamEvents() liefert JobEvent-Objekte, bis ein terminales Event eintrifft. SpectrumInterface::request() gibt den rohen Response-Body als string zurück. probe() gibt einen HardwareReport zurück, und getBudget() gibt ein SpectrumBudget zurück.

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());
}

Der strukturierte Kontext wird dem Log-Eintrag übergeben, ohne dass die Meldung geparst werden muss.

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 '';
}
}

Das nullable SpectrumInterface macht die Beschleunigung optional. Der Contract stellt genau eine Transport-Methode bereit, request(), die den rohen Response-Body als string zurückgibt. Ein übergeordneter Parser wandelt diesen Body in ein NextPDF\Accelerator\BatchResult um. Der konkrete SpectrumClient fügt typisierte Helfer wie parseBatch() hinzu, die request() kapseln und direkt ein BatchResult zurückgeben. Diese Helfer sind nicht Teil des eingefrorenen Contracts. Die Degradation-Policy entscheidet, ob ein fehlender Sidecar fatal ist.

  • Nicht jedes \Throwable ist eine NextPDF-Exception. Sichern Sie immer mit instanceof ContextAwareExceptionInterface ab, bevor Sie getContext() aufrufen.
  • getContext() gibt laut Contract ausschließlich Primitive zurück. Ein Consumer, der verschachtelte Objekte erwartet, geht von einer falschen Annahme aus; der Contract garantiert JSON-sichere Werte.
  • SpectrumInterface::isAvailable() sitzt hinter einem Circuit-Breaker und kann gefahrlos häufig aufgerufen werden, doch ein true-Ergebnis ist eine zeitpunktbezogene Prüfung. Berücksichtigen Sie, dass ein Sidecar zwischen Prüfung und Aufruf wegfallen kann.
  • JobNotificationInterface::streamEvents() ist ein Generator. Wenn Sie ihn zweimal durchlaufen, werden die Events nicht erneut abgespielt. Verarbeiten Sie ihn nur einmal.
  • DegradationPolicy::Permissive wirft nie. In diesem Modus läuft eine Compliance-relevante Degradation still durch. Verwenden Sie diesen Modus nicht für regulierte Ausgaben.

Die Observability-Contracts verursachen vernachlässigbaren Overhead. getContext() gibt ein vorab erstelltes Array zurück. isAvailable() ist eine zwischengespeicherte, Circuit-Breaker-geschützte Health-Probe. Der Contract verlangt von Implementierungen, das Probe-Ergebnis mindestens 30 Sekunden zwischenzuspeichern, damit ein Hot Path den Sidecar nicht wiederholt aufruft. streamEvents() wird durch die Event-Rate des Sidecars begrenzt, nicht durch die Engine. Das performance_budget von 1500 ms Wall-Time und 64 MB Spitzenverbrauch ergibt sich aus der zugrunde liegenden Arbeit, die die Contracts beobachten, nicht aus den Contracts selbst. Das Reproduzierbarkeitsprofil ist structural. Ein Event-Stream und ein Exception-Kontext enthalten Zeitstempel. Zwei Läufe unterscheiden sich in diesen Feldern, während die Struktur identisch bleibt.

Strukturierter Exception-Kontext ist eine Angriffsfläche für Daten-Exfiltration, wenn er Geheimnisse enthält. Der Contract beschränkt den Kontext auf Primitive, wodurch das versehentliche Durchreichen von Objekten begrenzt wird. Ein Deployment muss sensible Werte trotzdem bereinigen, bevor der Kontext ein Log-Sink erreicht. Das ist die Safe-Telemetry-Pflicht in der Logging-Policy des Projekts. Der Beschleunigungs-Sidecar ist ein separater Prozess, der über einen Transport erreicht wird. Die Request-Methode übermittelt Scope-Claims für die Autorisierung. Ein Deployment muss die Sidecar-Grenze als Vertrauensgrenze behandeln. Eine Degradation-Policy, die auf Permissive gesetzt ist, kann einen sicherheitsrelevanten Capability-Verlust verschleiern. Verwenden Sie Strict, wo die Korrektheit der Ausgabe als Kontrolle dient. Behandeln Sie Exception-Kontext, Job-Events und Sidecar-Antworten als potenziell geloggte Daten und bereinigen Sie sie entsprechend.

Diese Seite stellt keinen direkten normativen Anspruch. Die Observability-Contracts legen den Engine-Zustand offen und implementieren kein standardisiertes Protokoll, dessen Klauseln die Engine zitieren müsste. Die oben genannten Pflichten zu Safe-Telemetry und Log-Bereinigung leiten sich aus der internen Logging-Policy des Projekts ab, nicht aus einem externen Standard. Wenn eine beobachtete Operation selbst standardisiert ist — eine Signatur, ein PDF/A-Dokument —, ist ihre Konformität auf der Signatur- oder Extraktionsseite dokumentiert.