Observability: hash-chained SIEM log and render reporting
At a glance
Section titled “At a glance”The Observability module provides the runtime-state implementation: a tamper-evident hash-chained security information and event management (SIEM) event log, render and pilot report aggregation, a hardware security module (HSM) audit log, and complete no-op metrics and trace implementations so instrumentation is always callable.
One canonical page per concern. The observability contracts —
ContextAwareExceptionInterface,SpectrumInterface,JobNotificationInterface, and theDegradationPolicyenum — are documented on Contracts / Observability. This page documents the concrete runtime-state implementation. The pages are complementary, not duplicates: use the contracts page for the service provider interface (SPI), and use this page for the SIEM log, reporting, and audit surfaces.
Install
Section titled “Install”composer require nextpdf/core:^3Conceptual overview
Section titled “Conceptual overview”This module turns the engine’s runtime state into durable, verifiable output.
HashChainSiemEventLog is the security-grade surface. It implements the
SiemEventEmitter contract and writes a JavaScript Object Notation (JSON)
Lines log where each record’s hash is
SHA-256(prev_hash_bytes || canonical_event_bytes). That linear hash-chain
makes the log tamper-evident: if any byte changes, a line is deleted, or lines
are reordered, the chain breaks. verifyIntegrity() walks the file and returns
the index of the first inconsistent record, or null when the chain is intact.
readAll() streams the records. A per-process advisory flock(LOCK_EX)
protects the read-tail-then-append critical section, so concurrent PHP
processes on the same file do not interleave records. The bound is explicit:
this is a linear hash-chain, not a Request for Comments (RFC) 6962 Merkle tree.
It is sufficient for tamper-evidence, not for efficient inclusion proofs. The
source says so. SiemEvent carries the typed event with toCanonicalJson().
SiemEventSeverity and SiemEventType classify it. CorrelationContext and
CorrelationIdGenerator carry a correlation id across related events.
RenderReportBuilder, RenderReport, PilotReportAggregator, and
PilotSummary make up the reporting surface (@since 5.1.0). The aggregator
collects RenderReports and produces a PilotSummary that renders to array,
JSON, or Markdown, in the form an operations review can use.
HsmAuditLogInterface / HsmAuditEvent record HSM-backed signing operations
for the security layer. The MetricsCounterInterface,
MetricsGaugeInterface, MetricsHistogramInterface, and
TraceSpanInterface define the metrics and trace shapes. The NoOp*
implementations provide a complete inert fallback, so the engine can emit
metrics and spans without a configured backend.
Stability: experimental. The SIEM log is internally cycle-tagged rather than carrying a frozen semantic versioning (semver)
@since, and the reporting surface is@since 5.1.0. The surfaces are functional and tested, but the application programming interface (API) shapes may evolve. Treat the log format (canonical JSON + hash-chain) as the stable contract and the PHP API as still settling.
API surface
Section titled “API surface”| Class | Key members | Role |
|---|---|---|
HashChainSiemEventLog | emit(SiemEvent), verifyIntegrity(): ?int, readAll(): Generator | Tamper-evident hash-chained SIEM log |
SiemEvent | toCanonicalJson() | Typed SIEM event |
SiemEventSeverity / SiemEventType (enums) | classification | Classifies event severity and type |
CorrelationContext / CorrelationIdGenerator | correlation threading | Threads correlation through related events |
RenderReportBuilder / RenderReport | report assembly | Builds per-render reports (@since 5.1.0) |
PilotReportAggregator | addReport(), count(), getSummary(), toJson(), toMarkdown(), exportReportsJson() | Aggregates render reports (@since 5.1.0) |
PilotSummary | toArray(), toJson(), toMarkdown() | Summarizes operations-review output (@since 5.1.0) |
HsmAuditLogInterface / HsmAuditEvent | HSM audit record | Records HSM operation audits |
NoOpSiemEventEmitter, NoOpMetricsCounter, NoOpTraceSpan, … | inert fallbacks | Provides complete no-op implementations |
Run composer docs:generate-api-php -- --module=Observability to generate the
full PHPDoc table.
Code sample — Quick start
Section titled “Code sample — Quick start”Emit an event and verify the integrity of the log.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Observability\Siem\HashChainSiemEventLog;use NextPDF\Observability\Siem\SiemEvent;
$log = new HashChainSiemEventLog('/var/log/nextpdf/siem.jsonl');$log->emit(new SiemEvent(/* type, severity, payload */));
$firstBroken = $log->verifyIntegrity();echo $firstBroken === null ? "SIEM chain intact.\n" : "Tamper detected at record {$firstBroken}.\n";Code sample — Production
Section titled “Code sample — Production”Wrap the emitter so a logging failure in the signing hot path stays a local decision instead of becoming an uncaught exception.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Observability\Siem\HashChainSiemEventLog;use NextPDF\Observability\Siem\Exception\SiemEmitterException;use NextPDF\Observability\Siem\SiemEvent;use Psr\Log\LoggerInterface;
final readonly class AuditedSiemSink{ public function __construct( private HashChainSiemEventLog $log, private LoggerInterface $fallback, ) {}
public function record(SiemEvent $event): void { try { $this->log->emit($event); } catch (SiemEmitterException $e) { // Do not let SIEM I/O abort the signing path; record and continue. $this->fallback->critical('SIEM emit failed; event not chained.', [ 'error' => $e->getMessage(), ]); } }}Edge cases & gotchas
Section titled “Edge cases & gotchas”emit()throwsSiemEmitterExceptionon write errors. A caller in the signing hot path must wrap it and decide locally whether to swallow, retry, or abort. The emitter does not decide for you.verifyIntegrity()returns the index of the first broken record, ornull. A non-null result means the log is compromised from that point. Do not trust records at or after it.- The advisory
flockis per-process and same-file. Cross-host concurrency needs an out-of-band sink, such as syslog forwarding. Do not assume the file lock coordinates across machines. - This is a linear hash-chain, not a Merkle tree. It provides tamper-evidence, not efficient inclusion proofs. Do not market it as the latter.
- The
NoOp*fallbacks are complete and inert. Do not branch on backend availability to “save work”. The no-op already costs nothing.
Performance
Section titled “Performance”emit() reads the previous record’s hash and appends one line under a file
lock: O(1) per event plus the lock. verifyIntegrity() is O(n) in the record
count because it walks the whole chain. Run it on a schedule, not in the hot
path. Reporting aggregation is linear in the number of reports. The
reproducibility profile is structural: events and reports carry timestamps
and correlation ids, so two runs differ in those fields while the chain
structure remains deterministic.
Security notes
Section titled “Security notes”The SIEM log is a security control. Its tamper-evidence depends on protecting
both the log file and the verification step: store the file on append-friendly,
access-controlled storage, run verifyIntegrity() on a schedule, and forward
records out-of-band so a host compromise cannot silently rewrite history.
Events can carry sensitive context. Apply the project’s log-scrubbing
obligation before constructing the event, not after chaining it, because a
scrubbed rewrite would break the chain. The HSM audit log records signing
operations and is itself security-relevant. Treat it with the same protections.
See the engine threat model in /modules/core/security/.
Conformance
Section titled “Conformance”This module makes no normative claim about PDF specifications. It implements
log-integrity and observability mechanisms whose design aligns with the
log-management and integrity-verification practices in NIST SP 800-92. That
control-framework alignment is documented in the source; it is not a
chunk-pinned PDF citation. The conformance of documents the engine produces
is validated by the oracle and golden suites described in
/modules/core/conformance/.
See also
Section titled “See also”- Contracts / Observability — the service provider interface (SPI): structured exceptions, Spectrum, and degradation policy.
- Telemetry module — the OpenTelemetry bridge for external backends.
- Audit module — the compliance-evidence exporter that pairs with the SIEM log.
- Security module — the signing operations recorded by the HSM audit log.
- Conformance overview