Skip to content

Commit 855f5fa

Browse files
stevebuxmasl2
andauthored
CCM-12845 Structure logs (#371)
* CCM-12845 Structure logs --------- Co-authored-by: Mark Slowey <113013138+masl2@users.noreply.github.com>
1 parent 2f9d9ae commit 855f5fa

39 files changed

Lines changed: 9812 additions & 14562 deletions

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": "^10.3.0",
34
"zod": "^4.1.11"
45
},
56
"description": "Common helper utilities for NHS Notify Supplier API",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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+
});

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: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@ describe("createDependenciesContainer", () => {
1515
jest.clearAllMocks();
1616
jest.resetModules();
1717

18-
// pino
19-
jest.mock("pino", () => ({
20-
__esModule: true,
21-
default: jest.fn(() => ({
18+
jest.mock("@internal/helpers", () => ({
19+
createLogger: jest.fn(() => ({
2220
info: jest.fn(),
2321
error: jest.fn(),
2422
warn: jest.fn(),
2523
debug: jest.fn(),
24+
level: "info",
2625
})),
2726
}));
2827

@@ -49,7 +48,7 @@ describe("createDependenciesContainer", () => {
4948
// get current mock instances
5049
const { S3Client } = jest.requireMock("@aws-sdk/client-s3");
5150
const { SQSClient } = jest.requireMock("@aws-sdk/client-sqs");
52-
const pinoMock = jest.requireMock("pino");
51+
const { createLogger } = jest.requireMock("@internal/helpers");
5352
const { LetterRepository, MIRepository } = jest.requireMock(
5453
"@internal/datastore",
5554
);
@@ -63,7 +62,7 @@ describe("createDependenciesContainer", () => {
6362

6463
expect(SQSClient).toHaveBeenCalledTimes(1);
6564

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

6867
expect(LetterRepository).toHaveBeenCalledTimes(1);
6968
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: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ export default function createGetLetterDataHandler(
3737
),
3838
);
3939

40+
const presignedUrl = await getLetterDataUrl(supplierId, letterId, deps);
41+
42+
deps.logger.info({
43+
description: "Generated presigned URL",
44+
supplierId,
45+
letterId,
46+
correlationId: commonIds.value.correlationId,
47+
});
48+
4049
emitForSingleSupplier(
4150
metrics,
4251
"getLetterData",
@@ -47,7 +56,7 @@ export default function createGetLetterDataHandler(
4756
return {
4857
statusCode: 303,
4958
headers: {
50-
Location: await getLetterDataUrl(supplierId, letterId, deps),
59+
Location: presignedUrl,
5160
},
5261
body: "",
5362
};

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export default function createGetLetterHandler(
5151
description: "Letter successfully fetched by id",
5252
supplierId,
5353
letterId,
54+
correlationId: commonIds.value.correlationId,
5455
});
5556

5657
emitForSingleSupplier(

0 commit comments

Comments
 (0)