feat(core-audit): MultiSinkAuditLog fan-out with settle-all + stderr fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
45
packages/core-audit/src/multi-sink-audit-log.ts
Normal file
45
packages/core-audit/src/multi-sink-audit-log.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { AuditEntry } from "@repo/core-shared/audit";
|
||||
import type { IAuditLog } from "./audit-log.interface";
|
||||
|
||||
/**
|
||||
* Fan-out wrapper. Delivers each entry to every inner sink with settle-all
|
||||
* semantics — one failing sink doesn't drop the audit entry from others.
|
||||
*
|
||||
* Failures emit a structured `audit-sink-error` JSON line to stderr.
|
||||
* Stderr (not via OTel/Sentry) avoids recursion: if Sentry is one of the
|
||||
* sinks failing and we routed the error back through Sentry's reporter,
|
||||
* we'd loop. Stderr is consumed by the same log shipper as audit entries
|
||||
* themselves, so the operator sees the failure in their aggregator.
|
||||
*/
|
||||
export class MultiSinkAuditLog implements IAuditLog {
|
||||
constructor(private readonly sinks: IAuditLog[]) {}
|
||||
|
||||
async record(entry: AuditEntry): Promise<void> {
|
||||
const results = await Promise.allSettled(this.sinks.map((s) => s.record(entry)));
|
||||
for (const r of results) {
|
||||
if (r.status === "rejected") {
|
||||
this.reportSinkError(r.reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async eraseSubject(actorId: string, mode: "pseudonymize" | "delete"): Promise<void> {
|
||||
const results = await Promise.allSettled(
|
||||
this.sinks.map((s) => s.eraseSubject(actorId, mode)),
|
||||
);
|
||||
for (const r of results) {
|
||||
if (r.status === "rejected") {
|
||||
this.reportSinkError(r.reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private reportSinkError(reason: unknown): void {
|
||||
const line = JSON.stringify({
|
||||
_type: "audit-sink-error",
|
||||
error: String(reason),
|
||||
at: new Date().toISOString(),
|
||||
});
|
||||
process.stderr.write(line + "\n");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user