feat: implement logging for durable deletion failures with job details

This commit is contained in:
FFXN 2026-08-05 18:57:12 +08:00
parent 3213741104
commit f1e79a7ad6
2 changed files with 52 additions and 0 deletions

View File

@ -10,6 +10,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import {
assertApiDurableDeletionDataReadiness,
createApiDurableDeletionAssembly,
writeDurableDeletionErrorLog,
} from "./durable-deletion-options";
type DatabaseExecuteInput = Parameters<
@ -137,6 +138,29 @@ describe("API durable deletion assembly", () => {
expect(repository.claimJobs).toHaveBeenCalledTimes(1);
expect(repository.claimOutbox).toHaveBeenCalledTimes(1);
});
it("logs durable deletion failures with job correlation and the original error", () => {
const write = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
writeDurableDeletionErrorLog({
error: new Error("derived data cleanup failed"),
job: {
checkpoint: "deleting_derived_data",
executionAttempts: 10,
id: "deletion-job-1",
knowledgeSpaceId: "knowledge-space-1",
runState: "failed",
targetId: "source-1",
targetType: "source",
} as never,
});
expect(write).toHaveBeenCalledOnce();
expect(write.mock.calls[0]?.[0]).toContain('"event":"knowledge_fs.durable_deletion.failed"');
expect(write.mock.calls[0]?.[0]).toContain('"jobId":"deletion-job-1"');
expect(write.mock.calls[0]?.[0]).toContain('"checkpoint":"deleting_derived_data"');
expect(write.mock.calls[0]?.[0]).toContain('"errorMessage":"derived data cleanup failed"');
});
});
function secretStoreStub(): Pick<SourceSecretStore, "delete"> {

View File

@ -1,6 +1,7 @@
import { randomUUID } from "node:crypto";
import {
type DurableDeletionJob,
type DurableDeletionOutboxDispatcher,
type DurableDeletionRepository,
type DurableDeletionRuntime,
@ -94,6 +95,7 @@ export function createApiDurableDeletionAssembly({
maxBatchSize: 10,
maxRetryDelayMs: 5 * 60_000,
maxStepsPerLease: 25,
onError: writeDurableDeletionErrorLog,
processor,
repository,
stepTimeoutMs: 5_000,
@ -124,3 +126,29 @@ export function createApiDurableDeletionAssembly({
},
};
}
export function writeDurableDeletionErrorLog(input: {
readonly error: unknown;
readonly job?: DurableDeletionJob | undefined;
}): void {
const error = input.error instanceof Error ? input.error : undefined;
process.stderr.write(
`${JSON.stringify({
checkpoint: input.job?.checkpoint,
errorMessage: boundedLogValue(error?.message ?? String(input.error), 16_384),
errorName: error?.name ?? "UnknownError",
errorStack: error?.stack ? boundedLogValue(error.stack, 32_768) : undefined,
event: "knowledge_fs.durable_deletion.failed",
executionAttempts: input.job?.executionAttempts,
jobId: input.job?.id,
knowledgeSpaceId: input.job?.knowledgeSpaceId,
runState: input.job?.runState,
targetId: input.job?.targetId,
targetType: input.job?.targetType,
})}\n`,
);
}
function boundedLogValue(value: string, limit: number): string {
return value.length <= limit ? value : `${value.slice(0, limit)}...[truncated]`;
}