-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(lambda): add support for log retention (#2067)
Adds a new property `logRetentionDays` on `Function` to control the log retention policy of the function logs in CloudWatch Logs. The implementation uses a Custom Resource to create the log group if it doesn't exist yet and to set the retention policy as discussed in #667. A retention policy of 1 day is set on the logs of the Lambda provider. The different retention days supported by CloudWatch Logs have been centralized in `@aws-cdk/aws-logs`. Some have been renamed to better match the console experience. Closes #667 BREAKING CHANGE: `cloudWatchLogsRetentionTimeDays` in `@aws-cdk/aws-cloudtrail` now uses a `logs.RetentionDays` instead of a `LogRetention`.
- Loading branch information
Showing
17 changed files
with
1,451 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
97 changes: 97 additions & 0 deletions
97
packages/@aws-cdk/aws-lambda/lib/log-retention-provider/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// tslint:disable:no-console | ||
import AWS = require('aws-sdk'); | ||
|
||
/** | ||
* Creates a log group and doesn't throw if it exists. | ||
* | ||
* @param logGroupName the name of the log group to create | ||
*/ | ||
async function createLogGroupSafe(logGroupName: string) { | ||
try { // Try to create the log group | ||
const cloudwatchlogs = new AWS.CloudWatchLogs({ apiVersion: '2014-03-28' }); | ||
await cloudwatchlogs.createLogGroup({ logGroupName }).promise(); | ||
} catch (e) { | ||
if (e.code !== 'ResourceAlreadyExistsException') { | ||
throw e; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Puts or deletes a retention policy on a log group. | ||
* | ||
* @param logGroupName the name of the log group to create | ||
* @param retentionInDays the number of days to retain the log events in the specified log group. | ||
*/ | ||
async function setRetentionPolicy(logGroupName: string, retentionInDays?: number) { | ||
const cloudwatchlogs = new AWS.CloudWatchLogs({ apiVersion: '2014-03-28' }); | ||
if (!retentionInDays) { | ||
await cloudwatchlogs.deleteRetentionPolicy({ logGroupName }).promise(); | ||
} else { | ||
await cloudwatchlogs.putRetentionPolicy({ logGroupName, retentionInDays }).promise(); | ||
} | ||
} | ||
|
||
export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent, context: AWSLambda.Context) { | ||
try { | ||
console.log(JSON.stringify(event)); | ||
|
||
// The target log group | ||
const logGroupName = event.ResourceProperties.LogGroupName; | ||
|
||
if (event.RequestType === 'Create' || event.RequestType === 'Update') { | ||
// Act on the target log group | ||
await createLogGroupSafe(logGroupName); | ||
await setRetentionPolicy(logGroupName, parseInt(event.ResourceProperties.RetentionInDays, 10)); | ||
|
||
if (event.RequestType === 'Create') { | ||
// Set a retention policy of 1 day on the logs of this function. The log | ||
// group for this function should already exist at this stage because we | ||
// already logged the event but due to the async nature of Lambda logging | ||
// there could be a race condition. So we also try to create the log group | ||
// of this function first. | ||
await createLogGroupSafe(`/aws/lambda/${context.functionName}`); | ||
await setRetentionPolicy(`/aws/lambda/${context.functionName}`, 1); | ||
} | ||
} | ||
|
||
await respond('SUCCESS', 'OK', logGroupName); | ||
} catch (e) { | ||
console.log(e); | ||
|
||
await respond('FAILED', e.message, event.ResourceProperties.LogGroupName); | ||
} | ||
|
||
function respond(responseStatus: string, reason: string, physicalResourceId: string) { | ||
const responseBody = JSON.stringify({ | ||
Status: responseStatus, | ||
Reason: reason, | ||
PhysicalResourceId: physicalResourceId, | ||
StackId: event.StackId, | ||
RequestId: event.RequestId, | ||
LogicalResourceId: event.LogicalResourceId, | ||
Data: {} | ||
}); | ||
|
||
console.log('Responding', responseBody); | ||
|
||
const parsedUrl = require('url').parse(event.ResponseURL); | ||
const requestOptions = { | ||
hostname: parsedUrl.hostname, | ||
path: parsedUrl.path, | ||
method: 'PUT', | ||
headers: { 'content-type': '', 'content-length': responseBody.length } | ||
}; | ||
|
||
return new Promise((resolve, reject) => { | ||
try { | ||
const request = require('https').request(requestOptions, resolve); | ||
request.on('error', reject); | ||
request.write(responseBody); | ||
request.end(); | ||
} catch (e) { | ||
reject(e); | ||
} | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import iam = require('@aws-cdk/aws-iam'); | ||
import logs = require('@aws-cdk/aws-logs'); | ||
import cdk = require('@aws-cdk/cdk'); | ||
import path = require('path'); | ||
import { Code } from './code'; | ||
import { Runtime } from './runtime'; | ||
import { SingletonFunction } from './singleton-lambda'; | ||
|
||
/** | ||
* Construction properties for a LogRetention. | ||
*/ | ||
export interface LogRetentionProps { | ||
/** | ||
* The log group name. | ||
*/ | ||
readonly logGroupName: string; | ||
|
||
/** | ||
* The number of days log events are kept in CloudWatch Logs. | ||
*/ | ||
readonly retentionDays: logs.RetentionDays; | ||
} | ||
|
||
/** | ||
* Creates a custom resource to control the retention policy of a CloudWatch Logs | ||
* log group. The log group is created if it doesn't already exist. The policy | ||
* is removed when `retentionDays` is `undefined` or equal to `Infinity`. | ||
*/ | ||
export class LogRetention extends cdk.Construct { | ||
constructor(scope: cdk.Construct, id: string, props: LogRetentionProps) { | ||
super(scope, id); | ||
|
||
// Custom resource provider | ||
const provider = new SingletonFunction(this, 'Provider', { | ||
code: Code.asset(path.join(__dirname, 'log-retention-provider')), | ||
runtime: Runtime.NodeJS810, | ||
handler: 'index.handler', | ||
uuid: 'aae0aa3c-5b4d-4f87-b02d-85b201efdd8a', | ||
lambdaPurpose: 'LogRetention', | ||
}); | ||
|
||
if (provider.role && !provider.role.node.tryFindChild('DefaultPolicy')) { // Avoid duplicate statements | ||
provider.role.addToPolicy( | ||
new iam.PolicyStatement() | ||
.addActions('logs:PutRetentionPolicy', 'logs:DeleteRetentionPolicy') | ||
// We need '*' here because we will also put a retention policy on | ||
// the log group of the provider function. Referencing it's name | ||
// creates a CF circular dependency. | ||
.addAllResources() | ||
); | ||
} | ||
|
||
// Need to use a CfnResource here to prevent lerna dependency cycles | ||
// @aws-cdk/aws-cloudformation -> @aws-cdk/aws-lambda -> @aws-cdk/aws-cloudformation | ||
new cdk.CfnResource(this, 'Resource', { | ||
type: 'Custom::LogRetention', | ||
properties: { | ||
ServiceToken: provider.functionArn, | ||
LogGroupName: props.logGroupName, | ||
RetentionInDays: props.retentionDays === Infinity ? undefined : props.retentionDays | ||
} | ||
}); | ||
} | ||
} |
Oops, something went wrong.