Skip to content

Commit ebaa29f

Browse files
authored
feat: add observability #5
feat: add observability #5
2 parents 05b23f7 + d389aeb commit ebaa29f

File tree

12 files changed

+834
-27
lines changed

12 files changed

+834
-27
lines changed

docs/API.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,92 @@ The tree node.
310310

311311
## Structs <a name="Structs" id="Structs"></a>
312312

313+
### AlarmProps <a name="AlarmProps" id="serverless-website-analytics.AlarmProps"></a>
314+
315+
#### Initializer <a name="Initializer" id="serverless-website-analytics.AlarmProps.Initializer"></a>
316+
317+
```typescript
318+
import { AlarmProps } from 'serverless-website-analytics'
319+
320+
const alarmProps: AlarmProps = { ... }
321+
```
322+
323+
#### Properties <a name="Properties" id="Properties"></a>
324+
325+
| **Name** | **Type** | **Description** |
326+
| --- | --- | --- |
327+
| <code><a href="#serverless-website-analytics.AlarmProps.property.alarmTopic">alarmTopic</a></code> | <code>aws-cdk-lib.aws_sns.Topic</code> | The SNS topic to send alarms to. |
328+
| <code><a href="#serverless-website-analytics.AlarmProps.property.alarmTypes">alarmTypes</a></code> | <code><a href="#serverless-website-analytics.AlarmTypes">AlarmTypes</a></code> | Specify which alarms you want. |
329+
330+
---
331+
332+
##### `alarmTopic`<sup>Required</sup> <a name="alarmTopic" id="serverless-website-analytics.AlarmProps.property.alarmTopic"></a>
333+
334+
```typescript
335+
public readonly alarmTopic: Topic;
336+
```
337+
338+
- *Type:* aws-cdk-lib.aws_sns.Topic
339+
340+
The SNS topic to send alarms to.
341+
342+
---
343+
344+
##### `alarmTypes`<sup>Required</sup> <a name="alarmTypes" id="serverless-website-analytics.AlarmProps.property.alarmTypes"></a>
345+
346+
```typescript
347+
public readonly alarmTypes: AlarmTypes;
348+
```
349+
350+
- *Type:* <a href="#serverless-website-analytics.AlarmTypes">AlarmTypes</a>
351+
352+
Specify which alarms you want.
353+
354+
---
355+
356+
### AlarmTypes <a name="AlarmTypes" id="serverless-website-analytics.AlarmTypes"></a>
357+
358+
#### Initializer <a name="Initializer" id="serverless-website-analytics.AlarmTypes.Initializer"></a>
359+
360+
```typescript
361+
import { AlarmTypes } from 'serverless-website-analytics'
362+
363+
const alarmTypes: AlarmTypes = { ... }
364+
```
365+
366+
#### Properties <a name="Properties" id="Properties"></a>
367+
368+
| **Name** | **Type** | **Description** |
369+
| --- | --- | --- |
370+
| <code><a href="#serverless-website-analytics.AlarmTypes.property.firehose">firehose</a></code> | <code>boolean</code> | Adds a throttle and s3 delivery failure alarms for both Firehoses. |
371+
| <code><a href="#serverless-website-analytics.AlarmTypes.property.lambda">lambda</a></code> | <code>boolean</code> | Adds a hard and soft alarm to both Lambda functions; |
372+
373+
---
374+
375+
##### `firehose`<sup>Required</sup> <a name="firehose" id="serverless-website-analytics.AlarmTypes.property.firehose"></a>
376+
377+
```typescript
378+
public readonly firehose: boolean;
379+
```
380+
381+
- *Type:* boolean
382+
383+
Adds a throttle and s3 delivery failure alarms for both Firehoses.
384+
385+
---
386+
387+
##### `lambda`<sup>Required</sup> <a name="lambda" id="serverless-website-analytics.AlarmTypes.property.lambda"></a>
388+
389+
```typescript
390+
public readonly lambda: boolean;
391+
```
392+
393+
- *Type:* boolean
394+
395+
Adds a hard and soft alarm to both Lambda functions;
396+
397+
---
398+
313399
### AwsEnv <a name="AwsEnv" id="serverless-website-analytics.AwsEnv"></a>
314400

315401
The AWS environment (account and region) to deploy to.
@@ -436,6 +522,49 @@ the serverless-website-analytics dashboard page.
436522

437523
---
438524

525+
### Observability <a name="Observability" id="serverless-website-analytics.Observability"></a>
526+
527+
#### Initializer <a name="Initializer" id="serverless-website-analytics.Observability.Initializer"></a>
528+
529+
```typescript
530+
import { Observability } from 'serverless-website-analytics'
531+
532+
const observability: Observability = { ... }
533+
```
534+
535+
#### Properties <a name="Properties" id="Properties"></a>
536+
537+
| **Name** | **Type** | **Description** |
538+
| --- | --- | --- |
539+
| <code><a href="#serverless-website-analytics.Observability.property.alarms">alarms</a></code> | <code><a href="#serverless-website-analytics.AlarmProps">AlarmProps</a></code> | Adds CloudWatch Alarms to the resources created by this construct. |
540+
| <code><a href="#serverless-website-analytics.Observability.property.dashboard">dashboard</a></code> | <code>boolean</code> | Adds a CloudWatch dashboard with metrics for the resources created by this construct. |
541+
542+
---
543+
544+
##### `alarms`<sup>Optional</sup> <a name="alarms" id="serverless-website-analytics.Observability.property.alarms"></a>
545+
546+
```typescript
547+
public readonly alarms: AlarmProps;
548+
```
549+
550+
- *Type:* <a href="#serverless-website-analytics.AlarmProps">AlarmProps</a>
551+
552+
Adds CloudWatch Alarms to the resources created by this construct.
553+
554+
---
555+
556+
##### `dashboard`<sup>Optional</sup> <a name="dashboard" id="serverless-website-analytics.Observability.property.dashboard"></a>
557+
558+
```typescript
559+
public readonly dashboard: boolean;
560+
```
561+
562+
- *Type:* boolean
563+
564+
Adds a CloudWatch dashboard with metrics for the resources created by this construct.
565+
566+
---
567+
439568
### SwaAuth <a name="SwaAuth" id="serverless-website-analytics.SwaAuth"></a>
440569

441570
The auth configuration which defaults to none.
@@ -648,6 +777,7 @@ const swaProps: SwaProps = { ... }
648777
| <code><a href="#serverless-website-analytics.SwaProps.property.auth">auth</a></code> | <code><a href="#serverless-website-analytics.SwaAuth">SwaAuth</a></code> | The auth configuration which defaults to none. |
649778
| <code><a href="#serverless-website-analytics.SwaProps.property.domain">domain</a></code> | <code><a href="#serverless-website-analytics.Domain">Domain</a></code> | If specified, it will create the CloudFront and Cognito resources at the specified domain and optionally create the DNS records in the specified Route53 hosted zone. |
650779
| <code><a href="#serverless-website-analytics.SwaProps.property.isDemoPage">isDemoPage</a></code> | <code>boolean</code> | If specified, adds the banner at the top of the page linking back to the open source project. |
780+
| <code><a href="#serverless-website-analytics.SwaProps.property.observability">observability</a></code> | <code><a href="#serverless-website-analytics.Observability">Observability</a></code> | Adds a CloudWatch Dashboard and Alarms if specified. |
651781

652782
---
653783

@@ -752,5 +882,17 @@ If specified, adds the banner at the top of the page linking back to the open so
752882

753883
---
754884

885+
##### `observability`<sup>Optional</sup> <a name="observability" id="serverless-website-analytics.SwaProps.property.observability"></a>
886+
887+
```typescript
888+
public readonly observability: Observability;
889+
```
890+
891+
- *Type:* <a href="#serverless-website-analytics.Observability">Observability</a>
892+
893+
Adds a CloudWatch Dashboard and Alarms if specified.
894+
895+
---
896+
755897

756898

src/auth.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ export function auth(scope: Construct, name: (name: string) => string, props: Sw
9494
},
9595
});
9696
userPoolDomain = domain.baseUrl();
97-
new cdk.CfnOutput(scope, name('COGNITO_HOSTED_UI_URL'), {
98-
description: 'COGNITO_HOSTED_UI_URL',
97+
new cdk.CfnOutput(scope, name('CognitoHostedUrl'), {
98+
description: 'Cognito Hosted Url',
9999
value: userPoolDomain,
100100
});
101101
} else {
@@ -118,9 +118,12 @@ export function auth(scope: Construct, name: (name: string) => string, props: Sw
118118
});
119119
}
120120

121-
new cdk.CfnOutput(scope, name('USER_POOL_ID'), { description: 'USER_POOL_ID', value: userPool.userPoolId });
122-
new cdk.CfnOutput(scope, name('USER_POOL_CLIENT_ID'), {
123-
description: 'USER_POOL_CLIENT_ID',
121+
new cdk.CfnOutput(scope, name('CognitoUserPoolId'), {
122+
description: 'Cognito UserPoolId',
123+
value: userPool.userPoolId,
124+
});
125+
new cdk.CfnOutput(scope, name('CognitoUserPoolClientId'), {
126+
description: 'Cognito UserPool ClientId',
124127
value: userPoolClient.userPoolClientId,
125128
});
126129
} else {

src/backend.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import * as cdk from 'aws-cdk-lib';
55
import * as iam from 'aws-cdk-lib/aws-iam';
66
import { Effect } from 'aws-cdk-lib/aws-iam';
77
import * as lambda from 'aws-cdk-lib/aws-lambda';
8+
import * as logs from 'aws-cdk-lib/aws-logs';
89
import { Construct } from 'constructs';
910
import { auth } from './auth';
1011
import { backendAnalytics } from './backendAnalytics';
1112
import { SwaProps } from './index';
13+
import { CwLambda } from './lib/cloudwatch-helper';
1214

1315
export function backend(
1416
scope: Construct,
@@ -194,8 +196,33 @@ export function backend(
194196
authType: lambda.FunctionUrlAuthType.NONE,
195197
});
196198

199+
const cwLambdas: CwLambda[] = [
200+
{
201+
func: apiIngestLambda,
202+
alarm: {
203+
hardError: true,
204+
softErrorFilter: logs.FilterPattern.all(
205+
logs.FilterPattern.stringValue('$.level', '=', 'audit'),
206+
logs.FilterPattern.stringValue('$.success', '=', 'false')
207+
),
208+
},
209+
},
210+
{
211+
func: apiFrontLambda,
212+
alarm: {
213+
hardError: true,
214+
softErrorFilter: logs.FilterPattern.all(
215+
logs.FilterPattern.stringValue('$.level', '=', 'audit'),
216+
logs.FilterPattern.stringValue('$.success', '=', 'false')
217+
),
218+
},
219+
},
220+
];
197221
return {
198222
apiIngestOrigin: cdk.Fn.select(2, cdk.Fn.split('/', apiIngestLambdaUrl.url)),
199223
apiFrontOrigin: cdk.Fn.select(2, cdk.Fn.split('/', apiFrontLambdaUrl.url)),
224+
observability: {
225+
cwLambdas,
226+
},
200227
};
201228
}

src/backendAnalytics.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { CfnLogGroup } from 'aws-cdk-lib/aws-logs';
99
import * as s3 from 'aws-cdk-lib/aws-s3';
1010
import { Construct } from 'constructs';
1111
import { SwaProps } from './index';
12+
import { CwBucket, CwFirehose } from './lib/cloudwatch-helper';
1213

1314
export function backendAnalytics(scope: Construct, name: (name: string) => string, props: SwaProps) {
1415
/* ======================================================================= */
@@ -464,29 +465,36 @@ export function backendAnalytics(scope: Construct, name: (name: string) => strin
464465
firehoseEvents.addDependency(firehoseDeliveryRole.node.defaultChild as CfnRole);
465466
firehoseEvents.addDependency(logGroup.node.defaultChild as CfnLogGroup);
466467

467-
new cdk.CfnOutput(scope, name('ANALYTICS_BUCKET'), {
468-
description: 'ANALYTICS_BUCKET',
468+
new cdk.CfnOutput(scope, name('AnalyticsBucket'), {
469+
description: 'Analytics Bucket',
469470
value: analyticsBucket.bucketName,
470471
});
471-
new cdk.CfnOutput(scope, name('FIREHOSE_PAGE_VIEWS_NAME'), {
472-
description: 'FIREHOSE_PAGE_VIEWS_NAME',
472+
new cdk.CfnOutput(scope, name('FirehosePageViewsName'), {
473+
description: 'Firehose Page Views Name',
473474
value: firehosePageViews.deliveryStreamName!,
474475
});
475-
new cdk.CfnOutput(scope, name('FIREHOSE_EVENTS_NAME'), {
476-
description: 'FIREHOSE_EVENTS_NAME',
476+
new cdk.CfnOutput(scope, name('FirehoseEventsName'), {
477+
description: 'Firehose Events Name',
477478
value: firehoseEvents.deliveryStreamName!,
478479
});
479-
new cdk.CfnOutput(scope, name('ANALYTICS_GLUE_DB_NAME'), {
480-
description: 'ANALYTICS_GLUE_DB_NAME',
480+
new cdk.CfnOutput(scope, name('AnalyticsGlueDbName'), {
481+
description: 'Analytics Glue DB Name',
481482
value: glueDbName,
482483
});
483484

485+
const cwBuckets: CwBucket[] = [{ bucket: analyticsBucket }];
486+
const cwFirehoses: CwFirehose[] = [{ firehose: firehosePageViews }, { firehose: firehoseEvents }];
487+
484488
return {
485489
glueDbName,
486490
glueTablePageViewsName,
487491
glueTableEventsName,
488492
analyticsBucket,
489493
firehosePageViews,
490494
firehoseEvents,
495+
observability: {
496+
cwBuckets,
497+
cwFirehoses,
498+
},
491499
};
492500
}

src/frontend.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@ import { Construct } from 'constructs';
1919
import { auth } from './auth';
2020
import { backend } from './backend';
2121
import { SwaProps } from './index';
22+
import { CwCloudFront } from './lib/cloudwatch-helper';
2223

23-
//eslint-disable-next-line @typescript-eslint/ban-ts-comment
24-
// @ts-ignore
2524
export function frontend(
2625
scope: Construct,
2726
name: (name: string) => string,
@@ -201,8 +200,8 @@ export function frontend(
201200
certificate: props.domain.certificate,
202201
},
203202
});
204-
new cdk.CfnOutput(scope, name('COGNITO_HOSTED_UI_URL'), {
205-
description: 'COGNITO_HOSTED_UI_URL',
203+
new cdk.CfnOutput(scope, name('CognitoHostedUrl'), {
204+
description: 'Cognito Hosted URL',
206205
value: cognitoDomain.baseUrl(),
207206
});
208207
if (cloudFrontRecord) cognitoDomain.node.addDependency(cloudFrontRecord);
@@ -232,29 +231,34 @@ export function frontend(
232231
if (manualRecords.length) {
233232
cdk.Annotations.of(scope).addInfo('DNS records need to be created manually, see all CloudFormation Outputs');
234233
for (const record of manualRecords) {
235-
new cdk.CfnOutput(scope, name(`DNS_${record.description}`), {
236-
description: `DNS_${record.description}`,
234+
new cdk.CfnOutput(scope, name(`Dns_${record.description}`), {
235+
description: `DNS ${record.description}`,
237236
value: `Manually Create DNS Record: ${record.name} ${record.type} ${record.value}`,
238237
});
239238
}
240239
}
241240
}
242241

243-
new cdk.CfnOutput(scope, name('CFD_ID'), { description: 'CFD_ID', value: frontendDist.distributionId });
244-
new cdk.CfnOutput(scope, name('FRONTEND_URL'), {
245-
description: 'FRONTEND_URL',
242+
new cdk.CfnOutput(scope, name('CloudFrontId'), { description: 'CloudFront Id', value: frontendDist.distributionId });
243+
new cdk.CfnOutput(scope, name('FrontendUrl'), {
244+
description: 'Frontend Url',
246245
value: cdk.Fn.join('', ['https://', props.domain?.name || frontendDist.distributionDomainName]),
247246
});
248-
new cdk.CfnOutput(scope, name('apiIngestUrl'), {
249-
description: 'apiIngestUrl',
247+
new cdk.CfnOutput(scope, name('ApiIngestUrl'), {
248+
description: 'Api Ingest Url',
250249
value: cdk.Fn.join('', ['https://', props.domain?.name || frontendDist.distributionDomainName, '/api-ingest']),
251250
});
252-
new cdk.CfnOutput(scope, name('apiFrontLambdaUrl'), {
253-
description: 'apiFrontLambdaUrl',
251+
new cdk.CfnOutput(scope, name('ApiFrontLambdaUrl'), {
252+
description: 'Api Front Lambda Url',
254253
value: cdk.Fn.join('', ['https://', props.domain?.name || frontendDist.distributionDomainName, '/api']),
255254
});
256255

256+
const cwCloudFronts: CwCloudFront[] = [{ distribution: frontendDist }];
257+
257258
return {
258259
frontendDist,
260+
observability: {
261+
cwCloudFronts,
262+
},
259263
};
260264
}

0 commit comments

Comments
 (0)