Skip to content

Commit b7ba36c

Browse files
committed
CCM-12845 Structure logs
1 parent 42b0816 commit b7ba36c

30 files changed

Lines changed: 304 additions & 143 deletions

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
"**/Thumbs.db": true,
1111
".github": false,
1212
".vscode": false
13-
}
13+
},
14+
"typescript.tsdk": "node_modules/typescript/lib"
1415
}

internal/helpers/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"dependencies": {
3+
"pino": "^9.7.0",
34
"zod": "^4.1.11"
45
},
56
"description": "Common helper utilities for NHS Notify Supplier API",
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { createLogger } from "../logger";
2+
3+
describe("createLogger", () => {
4+
it("should create a logger with default log level", () => {
5+
const logger = createLogger();
6+
7+
expect(logger.level).toBe("info");
8+
});
9+
10+
it("should create a logger with custom log level", () => {
11+
const logger = createLogger({ logLevel: "debug" });
12+
13+
expect(logger.level).toBe("debug");
14+
});
15+
16+
it("should have expected logging methods", () => {
17+
const logger = createLogger();
18+
19+
expect(typeof logger.info).toBe("function");
20+
expect(typeof logger.error).toBe("function");
21+
expect(typeof logger.warn).toBe("function");
22+
expect(typeof logger.debug).toBe("function");
23+
});
24+
});

internal/helpers/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
export * from "./version";
88
export { default as $Environment } from "./environment";
99
export * from "./id-ref";
10+
export * from "./logger";

internal/helpers/src/logger.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import pino, { Logger } from "pino";
2+
3+
export type LoggerOptions = {
4+
logLevel?: string;
5+
};
6+
7+
/**
8+
* Creates a configured pino logger instance for use across lambdas.
9+
*
10+
* @param options - Optional configuration for the logger
11+
* @param options.logLevel - The log level (defaults to "info")
12+
* @returns A configured pino Logger instance
13+
*/
14+
export function createLogger(options: LoggerOptions = {}): Logger {
15+
const { logLevel = "info" } = options;
16+
17+
return pino({
18+
level: logLevel,
19+
formatters: {
20+
level: (label) => {
21+
return { level: label.toUpperCase() };
22+
},
23+
},
24+
timestamp: () => `,"timestamp":"${new Date(Date.now()).toISOString()}"`,
25+
});
26+
}

lambdas/api-handler/src/config/__tests__/deps.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ describe("createDependenciesContainer", () => {
1515
jest.clearAllMocks();
1616
jest.resetModules();
1717

18-
// pino
19-
jest.mock("pino", () => ({
20-
__esModule: true,
21-
default: jest.fn(() => ({
18+
// @internal/helpers - createLogger
19+
jest.mock("@internal/helpers", () => ({
20+
createLogger: jest.fn(() => ({
2221
info: jest.fn(),
2322
error: jest.fn(),
2423
warn: jest.fn(),
2524
debug: jest.fn(),
25+
level: "info",
2626
})),
2727
}));
2828

@@ -49,7 +49,7 @@ describe("createDependenciesContainer", () => {
4949
// get current mock instances
5050
const { S3Client } = jest.requireMock("@aws-sdk/client-s3");
5151
const { SQSClient } = jest.requireMock("@aws-sdk/client-sqs");
52-
const pinoMock = jest.requireMock("pino");
52+
const { createLogger } = jest.requireMock("@internal/helpers");
5353
const { LetterRepository, MIRepository } = jest.requireMock(
5454
"@internal/datastore",
5555
);
@@ -63,7 +63,7 @@ describe("createDependenciesContainer", () => {
6363

6464
expect(SQSClient).toHaveBeenCalledTimes(1);
6565

66-
expect(pinoMock.default).toHaveBeenCalledTimes(1);
66+
expect(createLogger).toHaveBeenCalledTimes(1);
6767

6868
expect(LetterRepository).toHaveBeenCalledTimes(1);
6969
const letterRepoCtorArgs = LetterRepository.mock.calls[0];

lambdas/api-handler/src/config/deps.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import { S3Client } from "@aws-sdk/client-s3";
22
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
33
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
44
import { SQSClient } from "@aws-sdk/client-sqs";
5-
import pino from "pino";
5+
import { Logger } from "pino";
66
import {
77
DBHealthcheck,
88
LetterRepository,
99
MIRepository,
1010
} from "@internal/datastore";
11+
import { createLogger } from "@internal/helpers";
1112
import { EnvVars, envVars } from "./env";
1213

1314
export type Deps = {
@@ -16,7 +17,7 @@ export type Deps = {
1617
letterRepo: LetterRepository;
1718
miRepo: MIRepository;
1819
dbHealthcheck: DBHealthcheck;
19-
logger: pino.Logger;
20+
logger: Logger;
2021
env: EnvVars;
2122
};
2223

@@ -26,7 +27,7 @@ function createDocumentClient(): DynamoDBDocumentClient {
2627
}
2728

2829
function createLetterRepository(
29-
log: pino.Logger,
30+
log: Logger,
3031
environment: EnvVars,
3132
): LetterRepository {
3233
const config = {
@@ -46,10 +47,7 @@ function createDBHealthcheck(environment: EnvVars): DBHealthcheck {
4647
return new DBHealthcheck(createDocumentClient(), config);
4748
}
4849

49-
function createMIRepository(
50-
log: pino.Logger,
51-
environment: EnvVars,
52-
): MIRepository {
50+
function createMIRepository(log: Logger, environment: EnvVars): MIRepository {
5351
const config = {
5452
miTableName: environment.MI_TABLE_NAME,
5553
miTtlHours: environment.MI_TTL_HOURS,
@@ -59,7 +57,7 @@ function createMIRepository(
5957
}
6058

6159
export function createDependenciesContainer(): Deps {
62-
const log = pino();
60+
const log = createLogger({ logLevel: envVars.PINO_LOG_LEVEL });
6361

6462
return {
6563
s3Client: new S3Client(),

lambdas/api-handler/src/config/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const EnvVarsSchema = z.object({
1010
DOWNLOAD_URL_TTL_SECONDS: z.coerce.number().int(),
1111
MAX_LIMIT: z.coerce.number().int().optional(),
1212
QUEUE_URL: z.coerce.string().optional(),
13+
PINO_LOG_LEVEL: z.coerce.string().optional(),
1314
});
1415

1516
export type EnvVars = z.infer<typeof EnvVarsSchema>;

lambdas/api-handler/src/handlers/__tests__/letter-status-update.test.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,12 @@ describe("createLetterStatusUpdateHandler", () => {
143143
expect(mockedDeps.letterRepo.updateLetterStatus).toHaveBeenCalledWith(
144144
updateLetterCommands[0],
145145
);
146-
expect(mockedDeps.logger.error).toHaveBeenCalledWith(
147-
{
148-
err: mockError,
149-
messageId: "mid-id1",
150-
correlationId: "correlationId-id1",
151-
messageBody: '{"id":"id1","status":"ACCEPTED","supplierId":"s1"}',
152-
},
153-
"Error processing letter status update",
154-
);
146+
expect(mockedDeps.logger.error).toHaveBeenCalledWith({
147+
description: "Error processing letter status update",
148+
err: mockError,
149+
messageId: "mid-id1",
150+
correlationId: "correlationId-id1",
151+
messageBody: '{"id":"id1","status":"ACCEPTED","supplierId":"s1"}',
152+
});
155153
});
156154
});

lambdas/api-handler/src/handlers/get-letter-data.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,23 @@ export default function createGetLetterDataHandler(
3333
),
3434
);
3535

36+
const presignedUrl = await getLetterDataUrl(
37+
commonIds.value.supplierId,
38+
letterId,
39+
deps,
40+
);
41+
42+
deps.logger.info({
43+
description: "Generated presigned URL",
44+
supplierId: commonIds.value.supplierId,
45+
letterId,
46+
correlationId: commonIds.value.correlationId,
47+
});
48+
3649
return {
3750
statusCode: 303,
3851
headers: {
39-
Location: await getLetterDataUrl(
40-
commonIds.value.supplierId,
41-
letterId,
42-
deps,
43-
),
52+
Location: presignedUrl,
4453
},
4554
body: "",
4655
};

0 commit comments

Comments
 (0)