diff --git a/src/app.test.ts b/src/app.test.ts index 54e64a0..2cddb5e 100644 --- a/src/app.test.ts +++ b/src/app.test.ts @@ -67,3 +67,26 @@ describe("routes", () => { expect(b.headers["x-request-id"]).toMatch(/^[0-9a-f-]{36}$/); }); }); + +describe("log-demo", () => { + it("logs one JSON line with demo fields and the request id", async () => { + const lines: Record[] = []; + const captured = pino( + { base: { service: "test-svc" } }, + { write: (l: string) => lines.push(JSON.parse(l)) }, + ); + const app = createApp({ config: parseConfig(base), logger: captured }); + const res = await request(app) + .get("/log-demo?msg=hello") + .set("X-Request-Id", "rid-demo"); + expect(res.status).toBe(200); + expect(res.body).toMatchObject({ logged: true, text: "hello" }); + const line = lines.find((l) => l.msg === "log-demo"); + expect(line).toMatchObject({ + service: "test-svc", + demo: true, + text: "hello", + req: { id: "rid-demo" }, + }); + }); +}); diff --git a/src/app.ts b/src/app.ts index dd409b3..ba2d677 100644 --- a/src/app.ts +++ b/src/app.ts @@ -6,6 +6,7 @@ import type { Config } from "./lib/config.js"; import type { Db } from "./lib/db.js"; import type { Cache } from "./lib/redis.js"; import { healthRouter } from "./routes/health.js"; +import { logDemoRouter } from "./routes/log-demo.js"; import { rootRouter } from "./routes/root.js"; export interface Deps { @@ -43,6 +44,7 @@ export function createApp({ config, logger, db, cache }: Deps) { app.use(healthRouter({ db, cache })); app.use(rootRouter({ config, db, cache })); + app.use(logDemoRouter()); // Express identifies error handlers by arity; keep all four args. // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/src/routes/log-demo.ts b/src/routes/log-demo.ts new file mode 100644 index 0000000..02356de --- /dev/null +++ b/src/routes/log-demo.ts @@ -0,0 +1,17 @@ +import { Router } from "express"; + +// Developer-story demo for the logging pipeline: every field in the first +// argument to req.log.* becomes a queryable field in Kibana (data view kube-*). +// The message string always wins over a `msg` key in the object, so the +// caller's text goes in `text`. +export function logDemoRouter(): Router { + const router = Router(); + + router.get("/log-demo", (req, res) => { + const text = typeof req.query.msg === "string" ? req.query.msg : ""; + req.log.info({ demo: true, text }, "log-demo"); + res.json({ logged: true, text, requestId: req.id }); + }); + + return router; +}