From e84bf1c530e8ef1117eeb3aad713d8cf373e1d60 Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Tue, 10 Feb 2026 12:42:55 +0000 Subject: [PATCH 1/6] log EMF object for letter-status-update failure --- .../api-handler/src/handlers/get-letters.ts | 1 - .../src/handlers/letter-status-update.ts | 17 +++++++++- lambdas/api-handler/src/utils/metrics.ts | 31 +++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/lambdas/api-handler/src/handlers/get-letters.ts b/lambdas/api-handler/src/handlers/get-letters.ts index 34d11bef9..54d4f8ae4 100644 --- a/lambdas/api-handler/src/handlers/get-letters.ts +++ b/lambdas/api-handler/src/handlers/get-letters.ts @@ -14,7 +14,6 @@ import { mapToGetLettersResponse } from "../mappers/letter-mapper"; import type { Deps } from "../config/deps"; import { MetricStatus, emitForSingleSupplier } from "../utils/metrics"; -// List letters Handlers // The endpoint should only return pending letters for now const status = "PENDING"; diff --git a/lambdas/api-handler/src/handlers/letter-status-update.ts b/lambdas/api-handler/src/handlers/letter-status-update.ts index fa14e672d..4a7fa62d5 100644 --- a/lambdas/api-handler/src/handlers/letter-status-update.ts +++ b/lambdas/api-handler/src/handlers/letter-status-update.ts @@ -1,10 +1,13 @@ -import { SQSEvent, SQSHandler } from "aws-lambda"; +import { SQSEvent, SQSHandler, SQSRecord } from "aws-lambda"; +import { Unit } from "aws-embedded-metrics"; +import pino from "pino"; import { UpdateLetterCommand, UpdateLetterCommandSchema, } from "../contracts/letters"; import { Deps } from "../config/deps"; import { mapToUpdateLetter } from "../mappers/letter-mapper"; +import { buildEMFObject } from "../utils/metrics"; export default function createLetterStatusUpdateHandler( deps: Deps, @@ -27,9 +30,21 @@ export default function createLetterStatusUpdateHandler( }, "Error processing letter status update", ); + // create metric object + emitAndFlushMetricLog(message, deps.logger); } }); await Promise.all(tasks); }; } + +function emitAndFlushMetricLog(message: SQSRecord, logger: pino.Logger) { + const metric = { + key: "statusUpdateFailed", + value: 1, + unit: Unit.Count, + }; + const emf = buildEMFObject("letter-status-update", {}, metric); + logger.info(emf); +} diff --git a/lambdas/api-handler/src/utils/metrics.ts b/lambdas/api-handler/src/utils/metrics.ts index 83a32c3a3..c0925495e 100644 --- a/lambdas/api-handler/src/utils/metrics.ts +++ b/lambdas/api-handler/src/utils/metrics.ts @@ -20,3 +20,34 @@ export enum MetricStatus { Success = "success", Failure = "failure", } + +interface MetricEntry { + key: string; + value: number; + unit: Unit; +} + +// build EMF object +export function buildEMFObject( + functionName: string, + dimensions: Record, + metric: MetricEntry, +) { + const namespace = process.env.AWS_LAMBDA_FUNCTION_NAME || functionName; + return { + LogGroup: namespace, + ServiceName: namespace, + ...dimensions, + _aws: { + Timestamp: Date.now(), + CloudWatchMetrics: [ + { + Namespace: namespace, + Dimensions: [[...Object.keys(dimensions), "ServiceName", "LogGroup"]], + Metrics: [{ Name: metric.key, Unit: metric.value }], + }, + ], + }, + [metric.key]: metric.value, + }; +} From 460aa00fc668c21961fb72ff7ae1db48bffcad24 Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Wed, 11 Feb 2026 11:34:14 +0000 Subject: [PATCH 2/6] log separate entries for batch post letters --- .../src/handlers/letter-status-update.ts | 4 +- .../api-handler/src/handlers/post-letters.ts | 166 ++++++++++-------- lambdas/api-handler/src/utils/metrics.ts | 2 +- 3 files changed, 92 insertions(+), 80 deletions(-) diff --git a/lambdas/api-handler/src/handlers/letter-status-update.ts b/lambdas/api-handler/src/handlers/letter-status-update.ts index 4a7fa62d5..acc3d44e1 100644 --- a/lambdas/api-handler/src/handlers/letter-status-update.ts +++ b/lambdas/api-handler/src/handlers/letter-status-update.ts @@ -7,7 +7,7 @@ import { } from "../contracts/letters"; import { Deps } from "../config/deps"; import { mapToUpdateLetter } from "../mappers/letter-mapper"; -import { buildEMFObject } from "../utils/metrics"; +import { buildEMFObject, MetricEntry } from "../utils/metrics"; export default function createLetterStatusUpdateHandler( deps: Deps, @@ -40,7 +40,7 @@ export default function createLetterStatusUpdateHandler( } function emitAndFlushMetricLog(message: SQSRecord, logger: pino.Logger) { - const metric = { + const metric: MetricEntry = { key: "statusUpdateFailed", value: 1, unit: Unit.Count, diff --git a/lambdas/api-handler/src/handlers/post-letters.ts b/lambdas/api-handler/src/handlers/post-letters.ts index 28e67a65e..87becbfe8 100644 --- a/lambdas/api-handler/src/handlers/post-letters.ts +++ b/lambdas/api-handler/src/handlers/post-letters.ts @@ -1,5 +1,6 @@ import { APIGatewayProxyHandler } from "aws-lambda"; -import { MetricsLogger, Unit, metricScope } from "aws-embedded-metrics"; +import { Unit } from "aws-embedded-metrics"; +import pino from "pino"; import type { Deps } from "../config/deps"; import { ApiErrorDetail } from "../contracts/errors"; import { @@ -13,7 +14,7 @@ import { mapToUpdateCommands } from "../mappers/letter-mapper"; import { enqueueLetterUpdateRequests } from "../services/letter-operations"; import { extractCommonIds } from "../utils/common-ids"; import { assertNotEmpty, requireEnvVar } from "../utils/validation"; -import { MetricStatus } from "../utils/metrics"; +import { MetricEntry, MetricStatus, buildEMFObject } from "../utils/metrics"; function duplicateIdsExist(postLettersRequest: PostLettersRequest) { const ids = postLettersRequest.data.map((item) => item.id); @@ -23,17 +24,23 @@ function duplicateIdsExist(postLettersRequest: PostLettersRequest) { /** * emits metrics of successful letter updates, including the supplier and grouped by status */ -function emitMetics( - metrics: MetricsLogger, +function emitSuccessMetrics( supplierId: string, statusesMapping: Map, + logger: pino.Logger, ) { for (const [status, count] of statusesMapping) { - metrics.putDimensions({ + const dimensions: Record = { supplier: supplierId, eventType: status, - }); - metrics.putMetric(MetricStatus.Success, count, Unit.Count); + }; + const metric: MetricEntry = { + key: "Letters posted", + value: count, + unit: Unit.Count, + }; + const emf = buildEMFObject("postLetters", dimensions, metric); + logger.info(emf); } } @@ -48,85 +55,90 @@ function populateStatusesMap(updateLetterCommands: UpdateLetterCommand[]) { export default function createPostLettersHandler( deps: Deps, ): APIGatewayProxyHandler { - return metricScope((metrics: MetricsLogger) => { - return async (event) => { - const commonIds = extractCommonIds( - event.headers, - event.requestContext, - deps, - ); + return async (event) => { + const commonIds = extractCommonIds( + event.headers, + event.requestContext, + deps, + ); - if (!commonIds.ok) { - return processError( - commonIds.error, - commonIds.correlationId, - deps.logger, - ); - } + if (!commonIds.ok) { + return processError( + commonIds.error, + commonIds.correlationId, + deps.logger, + ); + } - const maxUpdateItems = requireEnvVar(deps.env, "MAX_LIMIT"); - requireEnvVar(deps.env, "QUEUE_URL"); + const maxUpdateItems = requireEnvVar(deps.env, "MAX_LIMIT"); + requireEnvVar(deps.env, "QUEUE_URL"); - const { supplierId } = commonIds.value; - metrics.setNamespace( - process.env.AWS_LAMBDA_FUNCTION_NAME || "postLetters", + const { supplierId } = commonIds.value; + try { + const body = assertNotEmpty( + event.body, + new ValidationError(ApiErrorDetail.InvalidRequestMissingBody), ); + + let postLettersRequest: PostLettersRequest; + try { - const body = assertNotEmpty( - event.body, - new ValidationError(ApiErrorDetail.InvalidRequestMissingBody), - ); + postLettersRequest = PostLettersRequestSchema.parse(JSON.parse(body)); + } catch (error) { + const typedError = + error instanceof Error + ? new ValidationError(ApiErrorDetail.InvalidRequestBody, { + cause: error, + }) + : error; + throw typedError; + } - let postLettersRequest: PostLettersRequest; + if (postLettersRequest.data.length > maxUpdateItems) { + throw new ValidationError( + ApiErrorDetail.InvalidRequestLettersToUpdate, + { args: [maxUpdateItems] }, + ); + } - try { - postLettersRequest = PostLettersRequestSchema.parse(JSON.parse(body)); - } catch (error) { - const typedError = - error instanceof Error - ? new ValidationError(ApiErrorDetail.InvalidRequestBody, { - cause: error, - }) - : error; - throw typedError; - } + if (duplicateIdsExist(postLettersRequest)) { + throw new ValidationError( + ApiErrorDetail.InvalidRequestDuplicateLetterId, + ); + } - if (postLettersRequest.data.length > maxUpdateItems) { - throw new ValidationError( - ApiErrorDetail.InvalidRequestLettersToUpdate, - { args: [maxUpdateItems] }, - ); - } + const updateLetterCommands: UpdateLetterCommand[] = mapToUpdateCommands( + postLettersRequest, + supplierId, + ); + const statusesMapping = populateStatusesMap(updateLetterCommands); + await enqueueLetterUpdateRequests( + updateLetterCommands, + commonIds.value.correlationId, + deps, + ); - if (duplicateIdsExist(postLettersRequest)) { - throw new ValidationError( - ApiErrorDetail.InvalidRequestDuplicateLetterId, - ); - } + emitSuccessMetrics(supplierId, statusesMapping, deps.logger); + return { + statusCode: 202, + body: "", + }; + } catch (error) { + // error metrics + emitErrorMetrics(supplierId, deps.logger); - const updateLetterCommands: UpdateLetterCommand[] = mapToUpdateCommands( - postLettersRequest, - supplierId, - ); - const statusesMapping = populateStatusesMap(updateLetterCommands); - await enqueueLetterUpdateRequests( - updateLetterCommands, - commonIds.value.correlationId, - deps, - ); + return processError(error, commonIds.value.correlationId, deps.logger); + } + }; +} - emitMetics(metrics, supplierId, statusesMapping); - return { - statusCode: 202, - body: "", - }; - } catch (error) { - metrics.putDimensions({ - supplier: supplierId, - }); - metrics.putMetric(MetricStatus.Failure, 1, Unit.Count); - return processError(error, commonIds.value.correlationId, deps.logger); - } - }; - }); +function emitErrorMetrics(supplierId: string, logger: pino.Logger) { + const dimensions: Record = { supplier: supplierId }; + const metric: MetricEntry = { + key: MetricStatus.Failure, + value: 1, + unit: Unit.Count, + }; + const emf = buildEMFObject("postLetters", dimensions, metric); + logger.info(emf); } diff --git a/lambdas/api-handler/src/utils/metrics.ts b/lambdas/api-handler/src/utils/metrics.ts index c0925495e..1142f63c3 100644 --- a/lambdas/api-handler/src/utils/metrics.ts +++ b/lambdas/api-handler/src/utils/metrics.ts @@ -21,7 +21,7 @@ export enum MetricStatus { Failure = "failure", } -interface MetricEntry { +export interface MetricEntry { key: string; value: number; unit: Unit; From 56b8727c3cdbda046f3b30baf32f205983474213 Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Wed, 11 Feb 2026 12:19:44 +0000 Subject: [PATCH 3/6] fix lint error --- lambdas/api-handler/src/handlers/letter-status-update.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambdas/api-handler/src/handlers/letter-status-update.ts b/lambdas/api-handler/src/handlers/letter-status-update.ts index e184c2689..ffb6bd708 100644 --- a/lambdas/api-handler/src/handlers/letter-status-update.ts +++ b/lambdas/api-handler/src/handlers/letter-status-update.ts @@ -7,7 +7,7 @@ import { } from "../contracts/letters"; import { Deps } from "../config/deps"; import { mapToUpdateLetter } from "../mappers/letter-mapper"; -import { buildEMFObject, MetricEntry } from "../utils/metrics"; +import { MetricEntry, buildEMFObject } from "../utils/metrics"; export default function createLetterStatusUpdateHandler( deps: Deps, From a69a638f9d2183a94f626507dd1e221a834b8a47 Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Thu, 12 Feb 2026 09:52:20 +0000 Subject: [PATCH 4/6] log process.env for lambda runtime environment --- lambdas/api-handler/src/handlers/post-letters.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lambdas/api-handler/src/handlers/post-letters.ts b/lambdas/api-handler/src/handlers/post-letters.ts index 8c9b7225d..388128400 100644 --- a/lambdas/api-handler/src/handlers/post-letters.ts +++ b/lambdas/api-handler/src/handlers/post-letters.ts @@ -41,6 +41,7 @@ function emitSuccessMetrics( }; const emf = buildEMFObject("postLetters", dimensions, metric); logger.info(emf); + logger.info(`process.env: ${process.env}`); } } From aa23c01b950c1e446783bbb6fc2687dfb1a961f5 Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Thu, 12 Feb 2026 10:23:35 +0000 Subject: [PATCH 5/6] stringify the process.env object --- lambdas/api-handler/src/handlers/post-letters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambdas/api-handler/src/handlers/post-letters.ts b/lambdas/api-handler/src/handlers/post-letters.ts index 388128400..9ecef650b 100644 --- a/lambdas/api-handler/src/handlers/post-letters.ts +++ b/lambdas/api-handler/src/handlers/post-letters.ts @@ -41,7 +41,7 @@ function emitSuccessMetrics( }; const emf = buildEMFObject("postLetters", dimensions, metric); logger.info(emf); - logger.info(`process.env: ${process.env}`); + logger.info(`process.env: ${JSON.stringify(process.env)}`); } } From 8f3d6e33f9454c9af185b5d9aef1010d5298c9a3 Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Thu, 12 Feb 2026 12:57:11 +0000 Subject: [PATCH 6/6] correct metrics for the rest of the lambdas --- .../api-handler/src/handlers/patch-letter.ts | 14 ++- .../api-handler/src/handlers/post-letters.ts | 3 +- lambdas/api-handler/src/handlers/post-mi.ts | 13 +-- lambdas/api-handler/src/utils/metrics.ts | 6 +- .../src/letter-updates-transformer.ts | 105 ++++++++++-------- .../src/mi-updates-transformer.ts | 83 ++++++++------ 6 files changed, 124 insertions(+), 100 deletions(-) diff --git a/lambdas/api-handler/src/handlers/patch-letter.ts b/lambdas/api-handler/src/handlers/patch-letter.ts index fe906d145..fc71b67d5 100644 --- a/lambdas/api-handler/src/handlers/patch-letter.ts +++ b/lambdas/api-handler/src/handlers/patch-letter.ts @@ -56,6 +56,7 @@ export default function createPatchLetterHandler( try { patchLetterRequest = PatchLetterRequestSchema.parse(JSON.parse(body)); } catch (error) { + emitErrorMetric(metrics, supplierId); const typedError = error instanceof Error ? new ValidationError(ApiErrorDetail.InvalidRequestBody, { @@ -79,6 +80,7 @@ export default function createPatchLetterHandler( ); if (updateLetterCommand.id !== letterId) { + emitErrorMetric(metrics, supplierId); throw new ValidationError( ApiErrorDetail.InvalidRequestLetterIdsMismatch, ); @@ -100,12 +102,16 @@ export default function createPatchLetterHandler( body: "", }; } catch (error) { - metrics.putDimensions({ - supplier: supplierId, - }); - metrics.putMetric(MetricStatus.Success, 1, Unit.Count); + emitErrorMetric(metrics, supplierId); return processError(error, commonIds.value.correlationId, deps.logger); } }; }); } + +function emitErrorMetric(metrics: MetricsLogger, supplierId: string) { + metrics.putDimensions({ + supplier: supplierId, + }); + metrics.putMetric(MetricStatus.Failure, 1, Unit.Count); +} diff --git a/lambdas/api-handler/src/handlers/post-letters.ts b/lambdas/api-handler/src/handlers/post-letters.ts index 9ecef650b..bde478ea4 100644 --- a/lambdas/api-handler/src/handlers/post-letters.ts +++ b/lambdas/api-handler/src/handlers/post-letters.ts @@ -32,7 +32,7 @@ function emitSuccessMetrics( for (const [status, count] of statusesMapping) { const dimensions: Record = { supplier: supplierId, - eventType: status, + status, }; const metric: MetricEntry = { key: "Letters posted", @@ -41,7 +41,6 @@ function emitSuccessMetrics( }; const emf = buildEMFObject("postLetters", dimensions, metric); logger.info(emf); - logger.info(`process.env: ${JSON.stringify(process.env)}`); } } diff --git a/lambdas/api-handler/src/handlers/post-mi.ts b/lambdas/api-handler/src/handlers/post-mi.ts index 0d17512b0..727a4eca7 100644 --- a/lambdas/api-handler/src/handlers/post-mi.ts +++ b/lambdas/api-handler/src/handlers/post-mi.ts @@ -42,6 +42,7 @@ export default function createPostMIHandler( try { postMIRequest = PostMIRequestSchema.parse(JSON.parse(body)); } catch (error) { + emitErrorMetric(metrics, supplierId); const typedError = error instanceof Error ? new ValidationError(ApiErrorDetail.InvalidRequestBody, { @@ -86,15 +87,13 @@ export default function createPostMIHandler( body: JSON.stringify(result, null, 2), }; } catch (error) { - emitForSingleSupplier( - metrics, - "postMi", - supplierId, - 1, - MetricStatus.Failure, - ); + emitErrorMetric(metrics, supplierId); return processError(error, commonIds.value.correlationId, deps.logger); } }; }); } + +function emitErrorMetric(metrics: MetricsLogger, supplierId: string) { + emitForSingleSupplier(metrics, "postMi", supplierId, 1, MetricStatus.Failure); +} diff --git a/lambdas/api-handler/src/utils/metrics.ts b/lambdas/api-handler/src/utils/metrics.ts index 1142f63c3..4f820ddae 100644 --- a/lambdas/api-handler/src/utils/metrics.ts +++ b/lambdas/api-handler/src/utils/metrics.ts @@ -43,8 +43,10 @@ export function buildEMFObject( CloudWatchMetrics: [ { Namespace: namespace, - Dimensions: [[...Object.keys(dimensions), "ServiceName", "LogGroup"]], - Metrics: [{ Name: metric.key, Unit: metric.value }], + Dimensions: [...Object.keys(dimensions), "ServiceName", "LogGroup"], + Metrics: [ + { Name: metric.key, Value: metric.value, Unit: metric.unit }, + ], }, ], }, diff --git a/lambdas/letter-updates-transformer/src/letter-updates-transformer.ts b/lambdas/letter-updates-transformer/src/letter-updates-transformer.ts index 11b83c3b9..5dc7966ca 100644 --- a/lambdas/letter-updates-transformer/src/letter-updates-transformer.ts +++ b/lambdas/letter-updates-transformer/src/letter-updates-transformer.ts @@ -10,7 +10,8 @@ import { PublishBatchRequestEntry, } from "@aws-sdk/client-sns"; import { LetterEvent } from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src"; -import { MetricsLogger, Unit, metricScope } from "aws-embedded-metrics"; +import { Unit } from "aws-embedded-metrics"; +import pino from "pino"; import mapLetterToCloudEvent from "./mappers/letter-mapper"; import { Deps } from "./deps"; import { LetterForEventPub, LetterSchemaForEventPub } from "./types"; @@ -19,46 +20,42 @@ import { LetterForEventPub, LetterSchemaForEventPub } from "./types"; const BATCH_SIZE = 10; export default function createHandler(deps: Deps): Handler { - return metricScope((metrics: MetricsLogger) => { - return async (streamEvent: KinesisStreamEvent) => { - deps.logger.info({ description: "Received event", streamEvent }); + return async (streamEvent: KinesisStreamEvent) => { + deps.logger.info({ description: "Received event", streamEvent }); + deps.logger.info({ + description: "Number of records", + count: streamEvent.Records?.length || 0, + }); + + // Ensure logging by extracting all records first + const ddbRecords: DynamoDBRecord[] = streamEvent.Records.map((record) => + extractPayload(record, deps), + ); + + const cloudEvents: LetterEvent[] = ddbRecords + .filter((record) => filterRecord(record, deps)) + .map((element) => extractNewLetter(element)) + .map((element) => mapLetterToCloudEvent(element, deps.env.EVENT_SOURCE)); + + const eventTypeCount: Map = + populateEventTypeMap(cloudEvents); + for (const batch of generateBatches(cloudEvents)) { deps.logger.info({ - description: "Number of records", - count: streamEvent.Records?.length || 0, + description: "Publishing batch", + size: batch.length, + letterEvents: batch, }); - - // Ensure logging by extracting all records first - const ddbRecords: DynamoDBRecord[] = streamEvent.Records.map((record) => - extractPayload(record, deps), + await deps.snsClient.send( + new PublishBatchCommand({ + TopicArn: deps.env.EVENTPUB_SNS_TOPIC_ARN, + PublishBatchRequestEntries: batch.map((element, index) => + buildMessage(element, index), + ), + }), ); - - const cloudEvents: LetterEvent[] = ddbRecords - .filter((record) => filterRecord(record, deps)) - .map((element) => extractNewLetter(element)) - .map((element) => - mapLetterToCloudEvent(element, deps.env.EVENT_SOURCE), - ); - - const eventTypeCount: Map = - populateEventTypeMap(cloudEvents); - for (const batch of generateBatches(cloudEvents)) { - deps.logger.info({ - description: "Publishing batch", - size: batch.length, - letterEvents: batch, - }); - await deps.snsClient.send( - new PublishBatchCommand({ - TopicArn: deps.env.EVENTPUB_SNS_TOPIC_ARN, - PublishBatchRequestEntries: batch.map((element, index) => - buildMessage(element, index), - ), - }), - ); - } - emitMetrics(metrics, eventTypeCount); - }; - }); + } + emitMetrics(deps.logger, eventTypeCount); + }; } function populateEventTypeMap(cloudEvents: LetterEvent[]) { @@ -69,18 +66,30 @@ function populateEventTypeMap(cloudEvents: LetterEvent[]) { return evtMap; } -function emitMetrics( - metrics: MetricsLogger, - eventTypeCount: Map, -) { - metrics.setNamespace( - process.env.AWS_LAMBDA_FUNCTION_NAME || "letter-updates-transformer", - ); +function emitMetrics(logger: pino.Logger, eventTypeCount: Map) { + const namespace = + process.env.AWS_LAMBDA_FUNCTION_NAME || "letter-updates-transformer"; + for (const [type, count] of eventTypeCount) { - metrics.putDimensions({ + const emf = { + LogGroup: namespace, + ServiceName: namespace, eventType: type, - }); - metrics.putMetric("events published", count, Unit.Count); + _aws: { + Timestamp: Date.now(), + CloudWatchMetrics: [ + { + Namespace: namespace, + Dimensions: ["eventType", "ServiceName", "LogGroup"], + Metrics: [ + { Name: "events published", Value: count, Unit: Unit.Count }, + ], + }, + ], + }, + "events published": count, + }; + logger.info(emf); } } diff --git a/lambdas/mi-updates-transformer/src/mi-updates-transformer.ts b/lambdas/mi-updates-transformer/src/mi-updates-transformer.ts index aa02fcdf3..344dd937f 100644 --- a/lambdas/mi-updates-transformer/src/mi-updates-transformer.ts +++ b/lambdas/mi-updates-transformer/src/mi-updates-transformer.ts @@ -11,7 +11,8 @@ import { PublishBatchRequestEntry, } from "@aws-sdk/client-sns"; import { MISubmittedEvent } from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src"; -import { MetricsLogger, Unit, metricScope } from "aws-embedded-metrics"; +import { Unit } from "aws-embedded-metrics"; +import pino from "pino"; import { mapMIToCloudEvent } from "./mappers/mi-mapper"; import { Deps } from "./deps"; @@ -50,49 +51,57 @@ function extractMIData(record: DynamoDBRecord): MI { return MISchema.parse(unmarshall(newImage as any)); } -function emitMetrics( - metrics: MetricsLogger, - eventTypeCount: Map, -) { - metrics.setNamespace( - process.env.AWS_LAMBDA_FUNCTION_NAME || "letter-updates-transformer", - ); +function emitMetrics(logger: pino.Logger, eventTypeCount: Map) { + const namespace = + process.env.AWS_LAMBDA_FUNCTION_NAME || "mi-updates-transformer"; for (const [type, count] of eventTypeCount) { - metrics.putDimensions({ + const emf = { + LogGroup: namespace, + ServiceName: namespace, eventType: type, - }); - metrics.putMetric("events published", count, Unit.Count); + _aws: { + Timestamp: Date.now(), + CloudWatchMetrics: [ + { + Namespace: namespace, + Dimensions: ["LogGroup", "ServiceName", "eventType"], + Metrics: [ + { Name: "events published", Value: count, Unit: Unit.Count }, + ], + }, + ], + }, + }; + logger.info(emf); } } export default function createHandler(deps: Deps): Handler { - return metricScope((metrics: MetricsLogger) => { - return async (streamEvent: KinesisStreamEvent) => { - deps.logger.info({ description: "Received event", streamEvent }); + return async (streamEvent: KinesisStreamEvent) => { + deps.logger.info({ description: "Received event", streamEvent }); - const cloudEvents: MISubmittedEvent[] = streamEvent.Records.map( - (record) => extractPayload(record, deps), - ) - .filter((record) => record.eventName === "INSERT") - .map((element) => extractMIData(element)) - .map((payload) => mapMIToCloudEvent(payload, deps)); + const cloudEvents: MISubmittedEvent[] = streamEvent.Records.map((record) => + extractPayload(record, deps), + ) + .filter((record) => record.eventName === "INSERT") + .map((element) => extractMIData(element)) + .map((payload) => mapMIToCloudEvent(payload, deps)); - const eventTypeCount = new Map(); - for (const batch of generateBatches(cloudEvents)) { - await deps.snsClient.send( - new PublishBatchCommand({ - TopicArn: deps.env.EVENTPUB_SNS_TOPIC_ARN, - PublishBatchRequestEntries: batch.map((element) => { - eventTypeCount.set( - element.type, - (eventTypeCount.get(element.type) || 0) + 1, - ); - return buildMessage(element, deps); - }), + const eventTypeCount = new Map(); + for (const batch of generateBatches(cloudEvents)) { + await deps.snsClient.send( + new PublishBatchCommand({ + TopicArn: deps.env.EVENTPUB_SNS_TOPIC_ARN, + PublishBatchRequestEntries: batch.map((element) => { + eventTypeCount.set( + element.type, + (eventTypeCount.get(element.type) || 0) + 1, + ); + return buildMessage(element, deps); }), - ); - } - emitMetrics(metrics, eventTypeCount); - }; - }); + }), + ); + } + emitMetrics(deps.logger, eventTypeCount); + }; }