mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 12:18:11 +00:00
feat(freight-api): enrich canonical request log line
Emit the request line as raw JSON on stdout (level/time/logger as fields) instead of through Nest's console logger, whose prefix made it unparsable. Collect data points via logCtx at the flow chokepoints: BaseRepository writes (status changes, creates, deletes), invoice transitions, payment intent lifecycle + outbound payment-service calls, booking/contract entry state, review-note reasons, signatures and OTP verify outcomes.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { Logger } from "@nestjs/common";
|
||||
import type { Repository } from "typeorm";
|
||||
import {
|
||||
BaseRepository,
|
||||
RequestLogMiddleware,
|
||||
getLogContext,
|
||||
logCtx,
|
||||
@@ -47,10 +49,13 @@ describe("logCtx", () => {
|
||||
|
||||
describe("RequestLogMiddleware", () => {
|
||||
it("emits one canonical JSON line carrying the collected context", () => {
|
||||
// Raw stdout, not the Nest logger — the line must be parsable JSON with no
|
||||
// "[Nest] … LOG [request]" prefix in front of it.
|
||||
const lines: string[] = [];
|
||||
jest
|
||||
.spyOn(Logger.prototype, "warn")
|
||||
.mockImplementation((m) => lines.push(String(m)));
|
||||
jest.spyOn(process.stdout, "write").mockImplementation((chunk) => {
|
||||
lines.push(String(chunk));
|
||||
return true;
|
||||
});
|
||||
jest.spyOn(Logger.prototype, "log").mockImplementation(() => undefined);
|
||||
|
||||
const listeners: Record<string, () => void> = {};
|
||||
@@ -82,7 +87,11 @@ describe("RequestLogMiddleware", () => {
|
||||
listeners.close(); // aborts/close after finish must not double-log
|
||||
|
||||
expect(lines).toHaveLength(1);
|
||||
expect(lines[0].endsWith("\n")).toBe(true);
|
||||
expect(lines[0].startsWith("{")).toBe(true);
|
||||
expect(JSON.parse(lines[0])).toMatchObject({
|
||||
level: "warn",
|
||||
logger: "request",
|
||||
type: "http_request",
|
||||
requestId: "req-42",
|
||||
method: "POST",
|
||||
@@ -100,3 +109,38 @@ describe("RequestLogMiddleware", () => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
});
|
||||
|
||||
describe("BaseRepository write trail", () => {
|
||||
class TestRepo extends BaseRepository<{ id: string; status?: string }> {
|
||||
constructor(repo: Repository<{ id: string; status?: string }>) {
|
||||
super(repo);
|
||||
}
|
||||
}
|
||||
|
||||
const typeormRepo = {
|
||||
metadata: { tableName: "booking" },
|
||||
create: (data: unknown) => data,
|
||||
save: async (data: unknown) => data,
|
||||
update: async () => undefined,
|
||||
findOne: async () => ({ id: "b-1", status: "SUBMITTED" }),
|
||||
softDelete: async () => undefined,
|
||||
delete: async () => undefined,
|
||||
} as unknown as Repository<{ id: string; status?: string }>;
|
||||
|
||||
it("records creates, status changes and deletes without any service opting in", async () => {
|
||||
const ctx = await runWithLogContext({}, async () => {
|
||||
const repo = new TestRepo(typeormRepo);
|
||||
await repo.create({ id: "b-1" });
|
||||
await repo.update("b-1", { status: "SUBMITTED" });
|
||||
await repo.update("b-1", { id: "b-1" }); // no status → no transition entry
|
||||
await repo.softDelete("b-1");
|
||||
return getLogContext();
|
||||
});
|
||||
|
||||
expect(ctx).toEqual({
|
||||
db: { created: { booking: 1 }, updated: { booking: 2 } },
|
||||
statusChanges: [{ entity: "booking", id: "b-1", status: "SUBMITTED" }],
|
||||
deleted: [{ entity: "booking", id: "b-1" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user