cdk/lib/metric-push-api.ts (114 lines of code) (raw):
import { GuApiLambda } from '@guardian/cdk';
import { GuAlarm } from '@guardian/cdk/lib/constructs/cloudwatch';
import type { GuStackProps } from '@guardian/cdk/lib/constructs/core';
import { GuStack } from '@guardian/cdk/lib/constructs/core';
import { GuPutCloudwatchMetricsPolicy } from '@guardian/cdk/lib/constructs/iam';
import { type App, Duration } from 'aws-cdk-lib';
import { CfnBasePathMapping, CfnDomainName } from 'aws-cdk-lib/aws-apigateway';
import {
ComparisonOperator,
Metric,
TreatMissingData,
} from 'aws-cdk-lib/aws-cloudwatch';
import { CfnRecordSet } from 'aws-cdk-lib/aws-route53';
import { nodeVersion } from './node-version';
export class MetricPushApi extends GuStack {
constructor(scope: App, id: string, props: GuStackProps) {
super(scope, id, props);
const app = 'metric-push-api';
const nameWithStage = `${app}-${this.stage}`;
// Lambda & API Gateway
const commonEnvironmentVariables = {
App: app,
Stack: this.stack,
Stage: this.stage,
};
const lambda = new GuApiLambda(this, `${app}-lambda`, {
description:
'API triggered lambda to push a metric to cloudwatch so we can alarm on errors',
functionName: nameWithStage,
fileName: `${app}.zip`,
handler: 'index.handler',
runtime: nodeVersion,
memorySize: 512,
timeout: Duration.seconds(60),
environment: commonEnvironmentVariables,
// Create an alarm
monitoringConfiguration: {
noMonitoring: true,
},
app: app,
api: {
id: nameWithStage,
restApiName: nameWithStage,
description: `API Gateway endpoint for the ${nameWithStage} lambda`,
proxy: true,
deployOptions: {
stageName: this.stage,
},
},
});
const cloudwatchPutMetricPolicy = new GuPutCloudwatchMetricsPolicy(this);
lambda.role?.attachInlinePolicy(cloudwatchPutMetricPolicy);
// DNS
const domainName = new CfnDomainName(this, 'MetricPushDomainName', {
regionalCertificateArn: `arn:aws:acm:${this.region}:${this.account}:certificate/b384a6a0-2f54-4874-b99b-96eeff96c009`,
domainName: `metric-push-api-${this.stage.toLowerCase()}.support.guardianapis.com`,
endpointConfiguration: {
types: ['REGIONAL'],
},
});
new CfnBasePathMapping(this, 'MetricPushBasePathMapping', {
restApiId: lambda.api.restApiId,
domainName: domainName.ref,
stage: lambda.api.deploymentStage.stageName,
});
const dnsRecord = new CfnRecordSet(this, 'MetricPushDNSRecord', {
name: `metric-push-api-${this.stage.toLowerCase()}.support.guardianapis.com`,
type: 'CNAME',
comment: `CNAME for metric-push-api API ${this.stage}`,
hostedZoneName: 'support.guardianapis.com.',
ttl: '120',
resourceRecords: [domainName.attrRegionalDomainName],
});
dnsRecord.overrideLogicalId('MetricPushDNSRecord');
// Alarms
new GuAlarm(this, '5xxApiAlarm', {
app,
alarmName: `URGENT 9-5 - ${this.stage} ${nameWithStage} API Gateway is returning 5XX errors`,
threshold: 2,
evaluationPeriods: 1,
snsTopicName: `alarms-handler-topic-${this.stage}`,
actionsEnabled: this.stage === 'PROD',
treatMissingData: TreatMissingData.NOT_BREACHING,
comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
metric: new Metric({
metricName: '5XXError',
namespace: 'AWS/ApiGateway',
statistic: 'Sum',
period: Duration.seconds(60),
dimensionsMap: {
ApiName: nameWithStage,
},
}),
});
new GuAlarm(this, 'HighClientSideErrorRateAlarm', {
app,
alarmName: `URGENT 9-5 - ${this.stage} fatal client-side errors are being reported to sentry for support-frontend`,
alarmDescription: `Impact - some or all browsers are failing to render support client side pages. Log in to Sentry to see these errors: https://the-guardian.sentry.io/discover/results/?project=1213654&query="Fatal error rendering page"&queryDataset=error-events&sort=-count&statsPeriod=24h Follow the process in https://docs.google.com/document/d/1_3El3cly9d7u_jPgTcRjLxmdG2e919zCLvmcFCLOYAk/edit ${nameWithStage}`,
threshold: 2,
evaluationPeriods: 5,
datapointsToAlarm: 3,
snsTopicName: `alarms-handler-topic-${this.stage}`,
actionsEnabled: this.stage === 'PROD',
treatMissingData: TreatMissingData.NOT_BREACHING,
comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
metric: new Metric({
metricName: 'metric-push-api-client-side-error',
namespace: 'support-service-lambdas',
statistic: 'Sum',
period: Duration.seconds(60),
dimensionsMap: {
Stage: this.stage,
App: app,
},
}),
});
}
}