From 979da31dca8aa9dd8f3a662874c2cd177814ddad Mon Sep 17 00:00:00 2001 From: Kenji Kono Date: Fri, 20 Mar 2026 10:37:11 +0900 Subject: [PATCH] feat: add CloudWatch LogGroup with retention policy to Lambda functions Add explicit CDK-managed LogGroups with 1-week retention to all Lambda functions (Webapp Handler, MigrationRunner, AsyncJob Handler). Without this, Lambda auto-creates LogGroups with infinite retention, causing unbounded log storage costs. Lambda@Edge (SignPayload) is excluded since edge-region LogGroups cannot be managed by CDK and will be addressed separately (#66). Closes #103 --- cdk/lib/constructs/async-job.ts | 7 +++- cdk/lib/constructs/webapp.ts | 11 +++++- ...pp-starter-kit-without-domain.test.ts.snap | 39 +++++++++++++++++++ ...-fullstack-webapp-starter-kit.test.ts.snap | 39 +++++++++++++++++++ 4 files changed, 94 insertions(+), 2 deletions(-) diff --git a/cdk/lib/constructs/async-job.ts b/cdk/lib/constructs/async-job.ts index 25edc7c..59f6b61 100644 --- a/cdk/lib/constructs/async-job.ts +++ b/cdk/lib/constructs/async-job.ts @@ -1,5 +1,6 @@ import { Construct } from 'constructs'; -import { CfnOutput, Duration, TimeZone } from 'aws-cdk-lib'; +import { CfnOutput, Duration, RemovalPolicy, TimeZone } from 'aws-cdk-lib'; +import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs'; import { Architecture, DockerImageCode, DockerImageFunction, IFunction } from 'aws-cdk-lib/aws-lambda'; import { Platform } from 'aws-cdk-lib/aws-ecr-assets'; import { Database } from './database'; @@ -37,6 +38,10 @@ export class AsyncJob extends Construct { vpc: database.cluster.vpc, // limit concurrency to mitigate any possible EDoS attacks reservedConcurrentExecutions: 1, + logGroup: new LogGroup(this, 'HandlerLogs', { + retention: RetentionDays.ONE_WEEK, + removalPolicy: RemovalPolicy.DESTROY, + }), }); handler.connections.allowToDefaultPort(database); diff --git a/cdk/lib/constructs/webapp.ts b/cdk/lib/constructs/webapp.ts index 0f423b4..d378b19 100644 --- a/cdk/lib/constructs/webapp.ts +++ b/cdk/lib/constructs/webapp.ts @@ -1,4 +1,5 @@ -import { IgnoreMode, Duration, CfnOutput, Stack } from 'aws-cdk-lib'; +import { IgnoreMode, Duration, CfnOutput, Stack, RemovalPolicy } from 'aws-cdk-lib'; +import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs'; import { Platform } from 'aws-cdk-lib/aws-ecr-assets'; import { DockerImageFunction, DockerImageCode, Architecture } from 'aws-cdk-lib/aws-lambda'; import { Construct } from 'constructs'; @@ -84,6 +85,10 @@ export class Webapp extends Construct { vpc: database.cluster.vpc, memorySize: 1024, architecture: Architecture.ARM_64, + logGroup: new LogGroup(this, 'HandlerLogs', { + retention: RetentionDays.ONE_WEEK, + removalPolicy: RemovalPolicy.DESTROY, + }), }); handler.connections.allowToDefaultPort(database); asyncJob.handler.grantInvoke(handler); @@ -154,6 +159,10 @@ export class Webapp extends Construct { }, vpc: database.cluster.vpc, memorySize: 256, + logGroup: new LogGroup(this, 'MigrationRunnerLogs', { + retention: RetentionDays.ONE_WEEK, + removalPolicy: RemovalPolicy.DESTROY, + }), }); migrationRunner.connections.allowToDefaultPort(database); diff --git a/cdk/test/__snapshots__/serverless-fullstack-webapp-starter-kit-without-domain.test.ts.snap b/cdk/test/__snapshots__/serverless-fullstack-webapp-starter-kit-without-domain.test.ts.snap index ab57ed1..c3bca2c 100644 --- a/cdk/test/__snapshots__/serverless-fullstack-webapp-starter-kit-without-domain.test.ts.snap +++ b/cdk/test/__snapshots__/serverless-fullstack-webapp-starter-kit-without-domain.test.ts.snap @@ -809,6 +809,11 @@ exports[`Snapshot test 2`] = ` "async-job-runner.handler", ], }, + "LoggingConfig": { + "LogGroup": { + "Ref": "AsyncJobHandlerLogs20DFEE3E", + }, + }, "MemorySize": 256, "PackageType": "Image", "ReservedConcurrentExecutions": 1, @@ -843,6 +848,14 @@ exports[`Snapshot test 2`] = ` }, "Type": "AWS::Lambda::Function", }, + "AsyncJobHandlerLogs20DFEE3E": { + "DeletionPolicy": "Delete", + "Properties": { + "RetentionInDays": 7, + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete", + }, "AsyncJobHandlerSecurityGroupF59812E6": { "DependsOn": [ "VpcPrivateSubnet1DefaultRouteBE02A9ED", @@ -3466,6 +3479,11 @@ service iptables save", }, }, }, + "LoggingConfig": { + "LogGroup": { + "Ref": "WebappHandlerLogs87A6D2D7", + }, + }, "MemorySize": 1024, "PackageType": "Image", "Role": { @@ -3536,6 +3554,14 @@ service iptables save", }, "Type": "AWS::Lambda::Url", }, + "WebappHandlerLogs87A6D2D7": { + "DeletionPolicy": "Delete", + "Properties": { + "RetentionInDays": 7, + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete", + }, "WebappHandlerSecurityGroup5451B519": { "DependsOn": [ "VpcPrivateSubnet1DefaultRouteBE02A9ED", @@ -3829,6 +3855,11 @@ service iptables save", "migration-runner.handler", ], }, + "LoggingConfig": { + "LogGroup": { + "Ref": "WebappMigrationRunnerLogsD9A84B90", + }, + }, "MemorySize": 256, "PackageType": "Image", "Role": { @@ -3878,6 +3909,14 @@ service iptables save", }, "Type": "AWS::Lambda::Version", }, + "WebappMigrationRunnerLogsD9A84B90": { + "DeletionPolicy": "Delete", + "Properties": { + "RetentionInDays": 7, + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete", + }, "WebappMigrationRunnerSecurityGroup7F0DF264": { "DependsOn": [ "VpcPrivateSubnet1DefaultRouteBE02A9ED", diff --git a/cdk/test/__snapshots__/serverless-fullstack-webapp-starter-kit.test.ts.snap b/cdk/test/__snapshots__/serverless-fullstack-webapp-starter-kit.test.ts.snap index 0a17848..520dfb6 100644 --- a/cdk/test/__snapshots__/serverless-fullstack-webapp-starter-kit.test.ts.snap +++ b/cdk/test/__snapshots__/serverless-fullstack-webapp-starter-kit.test.ts.snap @@ -830,6 +830,11 @@ exports[`Snapshot test 2`] = ` "async-job-runner.handler", ], }, + "LoggingConfig": { + "LogGroup": { + "Ref": "AsyncJobHandlerLogs20DFEE3E", + }, + }, "MemorySize": 256, "PackageType": "Image", "ReservedConcurrentExecutions": 1, @@ -864,6 +869,14 @@ exports[`Snapshot test 2`] = ` }, "Type": "AWS::Lambda::Function", }, + "AsyncJobHandlerLogs20DFEE3E": { + "DeletionPolicy": "Delete", + "Properties": { + "RetentionInDays": 7, + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete", + }, "AsyncJobHandlerSecurityGroupF59812E6": { "DependsOn": [ "VpcPrivateSubnet1DefaultRouteBE02A9ED", @@ -3296,6 +3309,11 @@ service iptables save", }, }, }, + "LoggingConfig": { + "LogGroup": { + "Ref": "WebappHandlerLogs87A6D2D7", + }, + }, "MemorySize": 1024, "PackageType": "Image", "Role": { @@ -3366,6 +3384,14 @@ service iptables save", }, "Type": "AWS::Lambda::Url", }, + "WebappHandlerLogs87A6D2D7": { + "DeletionPolicy": "Delete", + "Properties": { + "RetentionInDays": 7, + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete", + }, "WebappHandlerSecurityGroup5451B519": { "DependsOn": [ "VpcPrivateSubnet1DefaultRouteBE02A9ED", @@ -3635,6 +3661,11 @@ service iptables save", "migration-runner.handler", ], }, + "LoggingConfig": { + "LogGroup": { + "Ref": "WebappMigrationRunnerLogsD9A84B90", + }, + }, "MemorySize": 256, "PackageType": "Image", "Role": { @@ -3684,6 +3715,14 @@ service iptables save", }, "Type": "AWS::Lambda::Version", }, + "WebappMigrationRunnerLogsD9A84B90": { + "DeletionPolicy": "Delete", + "Properties": { + "RetentionInDays": 7, + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete", + }, "WebappMigrationRunnerSecurityGroup7F0DF264": { "DependsOn": [ "VpcPrivateSubnet1DefaultRouteBE02A9ED",