Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(appsync): allow user to configure log retention time #21418

Merged
merged 11 commits into from
Aug 4, 2022
25 changes: 24 additions & 1 deletion packages/@aws-cdk/aws-appsync/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,29 @@ new route53.CnameRecord(this, `CnameApiRecord`, {
});
```

## Log Group

AppSync automatically create a log group with the name `/aws/appsync/apis/<graphql_api_id>` upon deployment with
log data set to never expire. If you want to set a different expiration period, use the `logConfig.retention` property.

To obtain the GraphQL API's log group as a `logs.ILogGroup` use the `logGroup` property of the
`GraphqlApi` construct.
nikovirtala marked this conversation as resolved.
Show resolved Hide resolved

```ts
import * as logs from '@aws-cdk/aws-logs';

const logConfig: appsync.LogConfig = {
retention: logs.RetentionDays.ONE_WEEK,
};

new appsync.GraphqlApi(this, 'api', {
authorizationConfig: {},
name: 'myApi',
schema: appsync.Schema.fromAsset(path.join(__dirname, 'myApi.graphql')),
logConfig,
});
```

## Schema

Every GraphQL Api needs a schema to define the Api. CDK offers `appsync.Schema`
Expand Down Expand Up @@ -427,7 +450,7 @@ new appsync.GraphqlApi(this, 'api', {
defaultAuthorization: {
authorizationType: appsync.AuthorizationType.LAMBDA,
lambdaAuthorizerConfig: {
handler: authFunction,
handler: authFunction,
// can also specify `resultsCacheTtl` and `validationRegex`.
},
},
Expand Down
29 changes: 28 additions & 1 deletion packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ICertificate } from '@aws-cdk/aws-certificatemanager';
import { IUserPool } from '@aws-cdk/aws-cognito';
import { ManagedPolicy, Role, IRole, ServicePrincipal, Grant, IGrantable } from '@aws-cdk/aws-iam';
import { IFunction } from '@aws-cdk/aws-lambda';
import { ILogGroup, LogGroup, LogRetention, RetentionDays } from '@aws-cdk/aws-logs';
import { ArnFormat, CfnResource, Duration, Expiration, IResolvable, Stack } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema, CfnDomainName, CfnDomainNameApiAssociation } from './appsync.generated';
Expand Down Expand Up @@ -248,6 +249,16 @@ export interface LogConfig {
* @default - None
*/
readonly role?: IRole;

/**
* The number of days log events are kept in CloudWatch Logs.
* By default AppSync keeps the logs infinitely. When updating this property,
* unsetting it doesn't remove the log retention policy.
* To remove the retention policy, set the value to `INFINITE`
*
* @default RetentionDays.INFINITE
*/
readonly retention?: RetentionDays
}

/**
Expand Down Expand Up @@ -459,6 +470,11 @@ export class GraphqlApi extends GraphqlApiBase {
*/
public readonly apiKey?: string;

/**
* the CloudWatch Log Group for this API
*/
public readonly logGroup: ILogGroup;

private schemaResource: CfnGraphQLSchema;
private api: CfnGraphQLApi;
private apiKeyResource?: CfnApiKey;
Expand Down Expand Up @@ -527,6 +543,16 @@ export class GraphqlApi extends GraphqlApiBase {
});
}

const logGroupName = `/aws/appsync/apis/${this.apiId}`;

this.logGroup = LogGroup.fromLogGroupName(this, 'LogGroup', logGroupName);

if (props.logConfig?.retention) {
new LogRetention(this, 'LogRetention', {
logGroupName: this.logGroup.logGroupName,
retention: props.logConfig.retention,
});
};
}

/**
Expand Down Expand Up @@ -620,10 +646,11 @@ export class GraphqlApi extends GraphqlApiBase {
ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSAppSyncPushToCloudWatchLogs'),
],
}).roleArn;
const fieldLogLevel: FieldLogLevel = config.fieldLogLevel ?? FieldLogLevel.NONE;
return {
cloudWatchLogsRoleArn: logsRoleArn,
excludeVerboseContent: config.excludeVerboseContent,
fieldLogLevel: config.fieldLogLevel,
fieldLogLevel: fieldLogLevel,
};
}

Expand Down
3 changes: 3 additions & 0 deletions packages/@aws-cdk/aws-appsync/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"@aws-cdk/aws-stepfunctions": "0.0.0",
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/integ-runner": "0.0.0",
"@aws-cdk/integ-tests": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.5.2",
Expand All @@ -97,6 +98,7 @@
"@aws-cdk/aws-elasticsearch": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/aws-logs": "0.0.0",
"@aws-cdk/aws-opensearchservice": "0.0.0",
"@aws-cdk/aws-rds": "0.0.0",
"@aws-cdk/aws-s3-assets": "0.0.0",
Expand All @@ -113,6 +115,7 @@
"@aws-cdk/aws-elasticsearch": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/aws-logs": "0.0.0",
"@aws-cdk/aws-opensearchservice": "0.0.0",
"@aws-cdk/aws-rds": "0.0.0",
"@aws-cdk/aws-s3-assets": "0.0.0",
Expand Down
47 changes: 47 additions & 0 deletions packages/@aws-cdk/aws-appsync/test/appsync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as path from 'path';
import { Template } from '@aws-cdk/assertions';
import { Certificate } from '@aws-cdk/aws-certificatemanager';
import * as iam from '@aws-cdk/aws-iam';
import * as logs from '@aws-cdk/aws-logs';
import * as cdk from '@aws-cdk/core';
import * as appsync from '../lib';

Expand Down Expand Up @@ -191,3 +192,49 @@ test('appsync GraphqlApi should be configured with custom domain when specified'
DomainName: domainName,
});
});

test('log retention should be configured with given retention time when specified', () => {
nikovirtala marked this conversation as resolved.
Show resolved Hide resolved
// GIVEN
const retentionTime = logs.RetentionDays.ONE_WEEK;

// WHEN
new appsync.GraphqlApi(stack, 'log-retention', {
authorizationConfig: {},
name: 'log-retention',
schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')),
logConfig: {
retention: retentionTime,
},
});

// THEN
Template.fromStack(stack).hasResourceProperties('Custom::LogRetention', {
LogGroupName: {
'Fn::Join': [
'',
[
'/aws/appsync/apis/',
{
'Fn::GetAtt': [
'logretentionB69DFB48',
'ApiId',
],
},
],
],
},
RetentionInDays: 7,
});
});

test('log retention should not appear when no retention time is specified', () => {
// WHEN
new appsync.GraphqlApi(stack, 'no-log-retention', {
authorizationConfig: {},
name: 'no-log-retention',
schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')),
});

// THEN
Template.fromStack(stack).resourceCountIs('Custom::LogRetention', 0);
});
40 changes: 40 additions & 0 deletions packages/@aws-cdk/aws-appsync/test/integ.log-retention.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { join } from 'path';
nikovirtala marked this conversation as resolved.
Show resolved Hide resolved
import { RetentionDays } from '@aws-cdk/aws-logs';
import { App, Stack } from '@aws-cdk/core';
import { ExpectedResult, IntegTest } from '@aws-cdk/integ-tests';
import { GraphqlApi, LogConfig, Schema } from '../lib';

const app = new App();
const stack = new Stack(app, 'AppSyncIntegLogRetention');


const retentionTime = RetentionDays.ONE_WEEK;
const logConfig: LogConfig = {
retention: retentionTime,
};

const api = new GraphqlApi(stack, 'GraphqlApi', {
authorizationConfig: {},
name: 'IntegLogRetention',
schema: Schema.fromAsset(join(__dirname, 'appsync.test.graphql')),
logConfig,
});

const integ = new IntegTest(app, 'Integ', { testCases: [stack] });

const describe = integ.assertions.awsApiCall('CloudWatchLogs',
'describeLogGroups',
{
logGroupNamePrefix: api.logGroup.logGroupName,
});

describe.expect(ExpectedResult.objectLike({
logGroups: [
{
logGroupName: api.logGroup.logGroupName,
retentionInDays: retentionTime,
},
],
}));

app.synth();
Loading