Setting the file. One moment. Alarm Template · AWS Observability · aws/agent-toolkit-for-aws · Skills Docs69
Creating Amazon Aurora Db Cluster With Instances
92
Routing Traffic With Route53 And CloudFront
Resilience Program Design
Debugging Lambda Timeouts
(opens in a new tab)
assets/cloudwatch/alarm-template.ts
TypeScript·105 lines·4 KB
11import { ITopic } from 'aws-cdk-lib/aws-sns';
12import { Construct } from 'constructs';
13
14/**
15 * Create Lambda monitoring with best-practice defaults.
16 *
17 * Best-practice defaults (vs common defaults):
18 * - evaluationPeriods: 3 (not 1) — reduces false positives
19 * - datapointsToAlarm: 2 (not 1) — M-of-N prevents flapping
20 * - treatMissingData: NOT_BREACHING (not MISSING) — absence of errors = OK
21 * - period: 60s (not 300s) — faster detection
22 * - error rate uses math expression (not raw Errors count)
23 * - duration uses p99 (not Average)
24 */
25export function createLambdaMonitoring(
26 scope: Construct,
27 fn: IFunction,
28 snsTopic: ITopic,
29 options?: {
30 errorRateThreshold?: number; // default: 5 (percent)
31 durationThresholdMs?: number; // default: 3000 (ms)
32 },
33) {
34 const errorRateThreshold = options?.errorRateThreshold ?? 5;
35 const durationThreshold = options?.durationThresholdMs ?? 3000;
36
37 // Error rate alarm (percentage via math expression)
38 const errorRateAlarm = new Alarm(scope, 'ErrorRateAlarm', {
39 metric: new MathExpression({
40 expression: 'IF(invocations > 0, errors * 100 / invocations, 0)',
41 usingMetrics: {
42 errors: fn.metricErrors({ period: Duration.minutes(1) }),
43 invocations: fn.metricInvocations({ period: Duration.minutes(1) }),
44 },
45 }),
46 threshold: errorRateThreshold,
47 evaluationPeriods: 3,
48 datapointsToAlarm: 2,
49 comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
50 treatMissingData: TreatMissingData.NOT_BREACHING,
51 });
52
53 // Duration alarm (p99, not average)
54 const durationAlarm = new Alarm(scope, 'DurationP99Alarm', {
55 metric: fn.metricDuration({
56 statistic: 'p99',
57 period: Duration.minutes(1),
58 }),
59 threshold: durationThreshold,
60 evaluationPeriods: 3,
61 datapointsToAlarm: 2,
62 comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
63 treatMissingData: TreatMissingData.NOT_BREACHING,
64 });
65
66 // Throttle alarm
67 const throttleAlarm = new Alarm(scope, 'ThrottleAlarm', {
68 metric: fn.metricThrottles({ period: Duration.minutes(1) }),
69 threshold: 1,
70 evaluationPeriods: 3,
71 datapointsToAlarm: 2,
72 comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
73 treatMissingData: TreatMissingData.NOT_BREACHING,
74 });
75
76 // Composite alarm — only page when service is unhealthy
77 const serviceHealthAlarm = new CompositeAlarm(scope, 'ServiceHealthAlarm', {
78 alarmRule: AlarmRule.anyOf(
79 AlarmRule.fromAlarm(errorRateAlarm, AlarmState.ALARM),
80 AlarmRule.fromAlarm(durationAlarm, AlarmState.ALARM),
81 AlarmRule.fromAlarm(throttleAlarm, AlarmState.ALARM),
82 ),
83 });
84 serviceHealthAlarm.addAlarmAction(new SnsAction(snsTopic));
85
86 // Dashboard
87 const dashboard = new Dashboard(scope, 'ServiceDashboard', {
88 start: '-PT8H',
89 periodOverride: PeriodOverride.INHERIT,
90 });
91 dashboard.addWidgets(
92 new TextWidget({ width: 24, height: 1, markdown: '# Service Health' }),
93 new AlarmWidget({ width: 8, height: 6, title: 'Error Rate', alarm: errorRateAlarm }),
94 new AlarmWidget({ width: 8, height: 6, title: 'Duration P99', alarm: durationAlarm }),
95 new AlarmWidget({ width: 8, height: 6, title: 'Throttles', alarm: throttleAlarm }),
96 new GraphWidget({
97 width: 24, height: 6,
98 title: 'Invocations & Errors',
99 left: [fn.metricInvocations({ period: Duration.minutes(1) })],
100 right: [fn.metricErrors({ period: Duration.minutes(1) })],
101 }),
102 );
103
104 return { errorRateAlarm, durationAlarm, throttleAlarm, serviceHealthAlarm, dashboard };
105}