Skip to content

Commit d47ea1a

Browse files
committed
feat: Create composite alarm with email alerts
1 parent ef896bd commit d47ea1a

File tree

1 file changed

+157
-133
lines changed

1 file changed

+157
-133
lines changed

lib/eventMonitoring.ts

Lines changed: 157 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ import { Construct } from 'constructs';
33
import { EventRouter } from './eventRouter';
44
import { EventProducer } from './eventProducer';
55
import { EventConsumer } from './eventConsumer';
6-
import { Alarm, Dashboard, GraphWidget, Metric, TextWidget, TreatMissingData } from 'aws-cdk-lib/aws-cloudwatch';
6+
import { Alarm, AlarmRule, CompositeAlarm, Dashboard, GraphWidget, Metric, TextWidget, TreatMissingData } from 'aws-cdk-lib/aws-cloudwatch';
77
import { Queue } from 'aws-cdk-lib/aws-sqs';
8+
import { EmailSubscription } from 'aws-cdk-lib/aws-sns-subscriptions';
9+
import { Topic } from 'aws-cdk-lib/aws-sns';
10+
import { ServicePrincipal } from 'aws-cdk-lib/aws-iam';
811

912
export interface EventMonitoringProps {
1013
router: EventRouter;
@@ -16,6 +19,7 @@ export interface EventMonitoringProps {
1619
export class EventMonitoring extends Construct {
1720
// Public properties
1821
public readonly dashboardName: string;
22+
private readonly alarms: Alarm[] = [];
1923

2024
constructor(scope: Construct, id: string, props?: EventMonitoringProps) {
2125
super(scope, id);
@@ -180,138 +184,158 @@ export class EventMonitoring extends Construct {
180184
});
181185
});
182186

183-
// Create a new dashboard
184-
const dashboard = new Dashboard(this, 'EventMonitoringDashboard', {
185-
dashboardName: `${id}-monitoring-dashboard`
186-
});
187-
188-
// Add a title
189-
dashboard.addWidgets(new TextWidget({
190-
markdown: '# Event-Driven Architecture Monitoring',
191-
width: 24,
192-
height: 1
193-
}));
194-
195-
// API Gateway metrics
196-
dashboard.addWidgets(
197-
new GraphWidget({
198-
title: 'API Gateway Metrics',
199-
width: 12,
200-
height: 6,
201-
left: [
202-
props?.producer.api.metricClientError()!,
203-
props?.producer.api.metricServerError()!,
204-
],
205-
right: [
206-
props?.producer.api.metricLatency()!
207-
]
208-
})
209-
);
210-
211-
// EventBridge Bus metrics
212-
dashboard.addWidgets(
213-
new GraphWidget({
214-
title: 'EventBridge Bus Metrics',
215-
width: 12,
216-
height: 6,
217-
left: [
218-
busInvocations,
219-
busFailedInvocations
220-
]
187+
// Create a new dashboard
188+
const dashboard = new Dashboard(this, 'EventMonitoringDashboard', {
189+
dashboardName: `${id}-monitoring-dashboard`
190+
});
191+
192+
// Add a title
193+
dashboard.addWidgets(new TextWidget({
194+
markdown: '# Event-Driven Architecture Monitoring',
195+
width: 24,
196+
height: 1
197+
}));
198+
199+
// API Gateway metrics
200+
dashboard.addWidgets(
201+
new GraphWidget({
202+
title: 'API Gateway Metrics',
203+
width: 12,
204+
height: 6,
205+
left: [
206+
props?.producer.api.metricClientError()!,
207+
props?.producer.api.metricServerError()!,
208+
],
209+
right: [
210+
props?.producer.api.metricLatency()!
211+
]
212+
})
213+
);
214+
215+
// EventBridge Bus metrics
216+
dashboard.addWidgets(
217+
new GraphWidget({
218+
title: 'EventBridge Bus Metrics',
219+
width: 12,
220+
height: 6,
221+
left: [
222+
busInvocations,
223+
busFailedInvocations
224+
]
225+
})
226+
);
227+
228+
// EventBridge Rule metrics
229+
const ruleWidgets = props?.router.rules?.map((rule) => {
230+
return new GraphWidget({
231+
title: `EventBridge Rule: ${rule.ruleName}`,
232+
width: 8,
233+
height: 6,
234+
left: [
235+
new Metric({
236+
namespace: 'AWS/Events',
237+
metricName: 'Invocations',
238+
dimensionsMap: { RuleName: rule.ruleName },
239+
period: cdk.Duration.minutes(5),
240+
statistic: 'Sum'
241+
}),
242+
new Metric({
243+
namespace: 'AWS/Events',
244+
metricName: 'FailedInvocations',
245+
dimensionsMap: { RuleName: rule.ruleName },
246+
period: cdk.Duration.minutes(5),
247+
statistic: 'Sum'
248+
}),
249+
new Metric({
250+
namespace: 'AWS/Events',
251+
metricName: 'ThrottledRules',
252+
dimensionsMap: { RuleName: rule.ruleName },
253+
period: cdk.Duration.minutes(5),
254+
statistic: 'Sum'
221255
})
222-
);
223-
224-
// EventBridge Rule metrics
225-
const ruleWidgets = props?.router.rules?.map((rule) => {
226-
return new GraphWidget({
227-
title: `EventBridge Rule: ${rule.ruleName}`,
228-
width: 8,
229-
height: 6,
230-
left: [
231-
new Metric({
232-
namespace: 'AWS/Events',
233-
metricName: 'Invocations',
234-
dimensionsMap: { RuleName: rule.ruleName },
235-
period: cdk.Duration.minutes(5),
236-
statistic: 'Sum'
237-
}),
238-
new Metric({
239-
namespace: 'AWS/Events',
240-
metricName: 'FailedInvocations',
241-
dimensionsMap: { RuleName: rule.ruleName },
242-
period: cdk.Duration.minutes(5),
243-
statistic: 'Sum'
244-
}),
245-
new Metric({
246-
namespace: 'AWS/Events',
247-
metricName: 'ThrottledRules',
248-
dimensionsMap: { RuleName: rule.ruleName },
249-
period: cdk.Duration.minutes(5),
250-
statistic: 'Sum'
251-
})
252-
]
253-
});
254-
}) || [];
255-
256-
if (ruleWidgets.length > 0) {
257-
dashboard.addWidgets(...ruleWidgets);
258-
}
259-
260-
// SNS Topic metrics
261-
const topicWidgets = props?.router.topics?.map((topic, index) => {
262-
return new GraphWidget({
263-
title: `SNS Topic: ${topic.topicName}`,
264-
width: 8,
265-
height: 6,
266-
left: [
267-
topic.metricNumberOfNotificationsFailed(),
268-
topic.metricNumberOfNotificationsDelivered(),
269-
topic.metricNumberOfMessagesPublished()
270-
]
271-
});
272-
}) || [];
273-
274-
if (topicWidgets.length > 0) {
275-
dashboard.addWidgets(...topicWidgets);
276-
}
277-
278-
// SQS Queue metrics for consumers
279-
const queueWidgets = props?.consumers?.map((consumer) => {
280-
return new GraphWidget({
281-
title: `SQS Queue: ${consumer.queue.queueName}`,
282-
width: 12,
283-
height: 6,
284-
left: [
285-
consumer.queue.metricApproximateNumberOfMessagesVisible(),
286-
consumer.queue.metricApproximateAgeOfOldestMessage(),
287-
consumer.queue.metricNumberOfMessagesReceived(),
288-
consumer.queue.metricNumberOfMessagesDeleted()
289-
]
290-
});
291-
}) || [];
292-
293-
if (queueWidgets.length > 0) {
294-
dashboard.addWidgets(...queueWidgets);
295-
}
296-
297-
// Dead Letter Queue metrics
298-
const dlqWidgets = props?.deadLetterQueues?.map((queue) => {
299-
return new GraphWidget({
300-
title: `Dead Letter Queue: ${queue.queueName}`,
301-
width: 12,
302-
height: 6,
303-
left: [
304-
queue.metricApproximateNumberOfMessagesVisible(),
305-
queue.metricApproximateAgeOfOldestMessage()
306-
]
307-
});
308-
}) || [];
309-
310-
if (dlqWidgets.length > 0) {
311-
dashboard.addWidgets(...dlqWidgets);
312-
}
313-
314-
// Store the dashboard name for reference
315-
this.dashboardName = dashboard.dashboardName;
256+
]
257+
});
258+
}) || [];
259+
260+
if (ruleWidgets.length > 0) {
261+
dashboard.addWidgets(...ruleWidgets);
262+
}
263+
264+
// SNS Topic metrics
265+
const topicWidgets = props?.router.topics?.map((topic, index) => {
266+
return new GraphWidget({
267+
title: `SNS Topic: ${topic.topicName}`,
268+
width: 8,
269+
height: 6,
270+
left: [
271+
topic.metricNumberOfNotificationsFailed(),
272+
topic.metricNumberOfNotificationsDelivered(),
273+
topic.metricNumberOfMessagesPublished()
274+
]
275+
});
276+
}) || [];
277+
278+
if (topicWidgets.length > 0) {
279+
dashboard.addWidgets(...topicWidgets);
280+
}
281+
282+
// SQS Queue metrics for consumers
283+
const queueWidgets = props?.consumers?.map((consumer) => {
284+
return new GraphWidget({
285+
title: `SQS Queue: ${consumer.queue.queueName}`,
286+
width: 12,
287+
height: 6,
288+
left: [
289+
consumer.queue.metricApproximateNumberOfMessagesVisible(),
290+
consumer.queue.metricApproximateAgeOfOldestMessage(),
291+
consumer.queue.metricNumberOfMessagesReceived(),
292+
consumer.queue.metricNumberOfMessagesDeleted()
293+
]
294+
});
295+
}) || [];
296+
297+
if (queueWidgets.length > 0) {
298+
dashboard.addWidgets(...queueWidgets);
299+
}
300+
301+
// Dead Letter Queue metrics
302+
const dlqWidgets = props?.deadLetterQueues?.map((queue) => {
303+
return new GraphWidget({
304+
title: `Dead Letter Queue: ${queue.queueName}`,
305+
width: 12,
306+
height: 6,
307+
left: [
308+
queue.metricApproximateNumberOfMessagesVisible(),
309+
queue.metricApproximateAgeOfOldestMessage()
310+
]
311+
});
312+
}) || [];
313+
314+
if (dlqWidgets.length > 0) {
315+
dashboard.addWidgets(...dlqWidgets);
316+
}
317+
318+
// Store the dashboard name for reference
319+
this.dashboardName = dashboard.dashboardName;
320+
321+
// Create composite alarm with alert action
322+
323+
// Create SNS topic for alarms
324+
const alarmTopic = new Topic(this, 'AlarmTopic', {
325+
displayName: 'Event Monitoring Alarms'
326+
});
327+
// Add email subscription
328+
alarmTopic.addSubscription(new EmailSubscription('[email protected]'));
329+
alarmTopic.grantPublish(new ServicePrincipal('events.amazonaws.com'));
330+
331+
// Create composite alarm
332+
const compositeAlarm = new CompositeAlarm(this, 'CompositeAlarm', {
333+
alarmRule: AlarmRule.anyOf(...this.alarms),
334+
alarmDescription: 'Composite alarm that triggers when any component alarm is in ALARM state',
335+
actionsEnabled: true
336+
});
337+
338+
// Add SNS action to composite alarm
339+
compositeAlarm.addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(alarmTopic));
316340
}
317341
}

0 commit comments

Comments
 (0)