From 58db34b5aa72df8cbdd395be0ea8272795a26bd2 Mon Sep 17 00:00:00 2001 From: Nico Steffens Date: Fri, 22 Aug 2025 18:11:59 +0200 Subject: [PATCH 1/2] Update logging options to use non-deprecated properties and create log group by default --- packages/backend-function/src/factory.test.ts | 41 +++++++++++++---- packages/backend-function/src/factory.ts | 14 +++++- .../src/logging_options_parser.test.ts | 45 ++++++++++++++++--- .../src/logging_options_parser.ts | 44 +++++++++++++++--- 4 files changed, 120 insertions(+), 24 deletions(-) diff --git a/packages/backend-function/src/factory.test.ts b/packages/backend-function/src/factory.test.ts index f3dbed08c9b..6a46f8cbf8d 100644 --- a/packages/backend-function/src/factory.test.ts +++ b/packages/backend-function/src/factory.test.ts @@ -578,7 +578,7 @@ void describe('AmplifyFunctionFactory', () => { }); void describe('logging options', () => { - void it('sets logging options', () => { + void it('sets logging options with log group', () => { const lambda = defineFunction({ entry: './test-assets/default-lambda/handler.ts', bundling: { @@ -591,15 +591,14 @@ void describe('AmplifyFunctionFactory', () => { }, }).getInstance(getInstanceProps); const template = Template.fromStack(lambda.stack); - // Enabling log retention adds extra lambda. - template.resourceCountIs('AWS::Lambda::Function', 2); - const lambdas = template.findResources('AWS::Lambda::Function'); - assert.ok( - Object.keys(lambdas).some((key) => key.startsWith('LogRetention')), - ); - template.hasResourceProperties('Custom::LogRetention', { + + // We now create a LogGroup directly rather than using the deprecated logRetention + template.resourceCountIs('AWS::Logs::LogGroup', 1); + template.hasResourceProperties('AWS::Logs::LogGroup', { RetentionInDays: 400, }); + + // The function should use the applicationLogLevelV2 and loggingFormat properties template.hasResourceProperties('AWS::Lambda::Function', { Handler: 'index.handler', LoggingConfig: { @@ -608,6 +607,32 @@ void describe('AmplifyFunctionFactory', () => { }, }); }); + + void it('sets logging options without retention', () => { + const lambda = defineFunction({ + entry: './test-assets/default-lambda/handler.ts', + bundling: { + minify: false, + }, + logging: { + format: 'json', + level: 'info', + }, + }).getInstance(getInstanceProps); + const template = Template.fromStack(lambda.stack); + + // No log group should be created when retention is not specified + template.resourceCountIs('AWS::Logs::LogGroup', 0); + + // The function should still have logging config + template.hasResourceProperties('AWS::Lambda::Function', { + Handler: 'index.handler', + LoggingConfig: { + ApplicationLogLevel: 'INFO', + LogFormat: 'JSON', + }, + }); + }); }); void describe('resourceAccessAcceptor', () => { diff --git a/packages/backend-function/src/factory.ts b/packages/backend-function/src/factory.ts index b24d42913ea..6e35ce3d136 100644 --- a/packages/backend-function/src/factory.ts +++ b/packages/backend-function/src/factory.ts @@ -46,7 +46,10 @@ import * as path from 'path'; import { FunctionEnvironmentTranslator } from './function_env_translator.js'; import { FunctionEnvironmentTypeGenerator } from './function_env_type_generator.js'; import { FunctionLayerArnParser } from './layer_parser.js'; -import { convertLoggingOptionsToCDK } from './logging_options_parser.js'; +import { + convertLoggingOptionsToCDK, + createLogGroup, +} from './logging_options_parser.js'; import { convertFunctionSchedulesToRuleSchedules } from './schedule_parser.js'; import { ProvidedFunctionFactory, @@ -583,6 +586,13 @@ class AmplifyFunction let functionLambda: NodejsFunction; const cdkLoggingOptions = convertLoggingOptionsToCDK(props.logging); + + // Create a log group if retention is specified (replacing deprecated logRetention property) + let logGroup = cdkLoggingOptions.logGroup; + if (props.logging.retention !== undefined && !logGroup) { + logGroup = createLogGroup(scope, id, props.logging); + } + try { functionLambda = new NodejsFunction(scope, `${id}-lambda`, { entry: props.entry, @@ -599,7 +609,7 @@ class AmplifyFunction externalModules: Object.keys(props.layers), logLevel: EsBuildLogLevel.ERROR, }, - logRetention: cdkLoggingOptions.retention, + logGroup: logGroup, applicationLogLevelV2: cdkLoggingOptions.level, loggingFormat: cdkLoggingOptions.format, }); diff --git a/packages/backend-function/src/logging_options_parser.test.ts b/packages/backend-function/src/logging_options_parser.test.ts index 4d6abec9589..3828c877f97 100644 --- a/packages/backend-function/src/logging_options_parser.test.ts +++ b/packages/backend-function/src/logging_options_parser.test.ts @@ -4,22 +4,23 @@ import { FunctionLoggingOptions } from './factory.js'; import { CDKLoggingOptions, convertLoggingOptionsToCDK, + createLogGroup, } from './logging_options_parser.js'; import { ApplicationLogLevel, LoggingFormat } from 'aws-cdk-lib/aws-lambda'; -import { RetentionDays } from 'aws-cdk-lib/aws-logs'; +import { LogGroup } from 'aws-cdk-lib/aws-logs'; +import { App, Stack } from 'aws-cdk-lib'; -type TestCase = { +type ConversionTestCase = { input: FunctionLoggingOptions; expectedOutput: CDKLoggingOptions; }; -const testCases: Array = [ +const conversionTestCases: Array = [ { input: {}, expectedOutput: { format: undefined, level: undefined, - retention: undefined, }, }, { @@ -29,7 +30,6 @@ const testCases: Array = [ }, expectedOutput: { format: LoggingFormat.TEXT, - retention: RetentionDays.THIRTEEN_MONTHS, level: undefined, }, }, @@ -40,17 +40,48 @@ const testCases: Array = [ }, expectedOutput: { format: LoggingFormat.JSON, - retention: undefined, level: ApplicationLogLevel.DEBUG, }, }, ]; void describe('LoggingOptions converter', () => { - testCases.forEach((testCase, index) => { + conversionTestCases.forEach((testCase, index) => { void it(`converts to cdk options[${index}]`, () => { const convertedOptions = convertLoggingOptionsToCDK(testCase.input); assert.deepStrictEqual(convertedOptions, testCase.expectedOutput); }); }); + + void it('createLogGroup creates a log group with proper retention', () => { + const app = new App(); + const stack = new Stack(app, 'TestStack'); + const logGroup = createLogGroup(stack, 'test-function', { + retention: '13 months', + }); + + // Check that the log group was created + assert.ok(logGroup instanceof LogGroup); + + // We can't easily check the CDK properties from the instance + // So we verify the synthesized CloudFormation template + const template = JSON.parse( + JSON.stringify(app.synth().getStackArtifact('TestStack').template), + ); + + // Find the LogGroup resource + const logGroupResources = Object.values(template.Resources).filter( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (resource: any) => resource.Type === 'AWS::Logs::LogGroup', + ); + + // Ensure we found exactly one log group + assert.strictEqual(logGroupResources.length, 1); + + const logGroupResource = logGroupResources[0] as { + // eslint-disable-next-line @typescript-eslint/naming-convention + Properties: { RetentionInDays: number }; + }; + assert.strictEqual(logGroupResource.Properties.RetentionInDays, 400); // 13 months = 400 days + }); }); diff --git a/packages/backend-function/src/logging_options_parser.ts b/packages/backend-function/src/logging_options_parser.ts index 028f7aa67db..80622c94fe3 100644 --- a/packages/backend-function/src/logging_options_parser.ts +++ b/packages/backend-function/src/logging_options_parser.ts @@ -4,16 +4,46 @@ import { LogLevelConverter, LogRetentionConverter, } from '@aws-amplify/platform-core/cdk'; -import { RetentionDays } from 'aws-cdk-lib/aws-logs'; +import { ILogGroup, LogGroup } from 'aws-cdk-lib/aws-logs'; +import { Construct } from 'constructs'; +import { RemovalPolicy } from 'aws-cdk-lib'; +/** + * CDK Lambda logging options using non-deprecated properties + * - level: ApplicationLogLevel for the lambda function (maps to applicationLogLevelV2) + * - format: Logging format (JSON or TEXT) + * - logGroup: Custom log group for the function (preferred over using logRetention directly) + */ export type CDKLoggingOptions = { level?: ApplicationLogLevel; - retention?: RetentionDays; format?: LoggingFormat; + logGroup?: ILogGroup; }; /** - * Converts logging options to CDK. + * Creates a LogGroup with the specified retention settings + * This replaces the deprecated logRetention property on NodejsFunction + */ +export const createLogGroup = ( + scope: Construct, + id: string, + loggingOptions: FunctionLoggingOptions, +): ILogGroup => { + const retentionDays = new LogRetentionConverter().toCDKRetentionDays( + loggingOptions.retention, + ); + + return new LogGroup(scope, `${id}-log-group`, { + retention: retentionDays, + removalPolicy: RemovalPolicy.DESTROY, // This matches Lambda's default for auto-created log groups + }); +}; + +/** + * Converts logging options to CDK format using non-deprecated properties + * Note: This function no longer includes 'retention' in the return object + * as that property is deprecated. Instead, create a LogGroup with + * createLogGroup() when needed. */ export const convertLoggingOptionsToCDK = ( loggingOptions: FunctionLoggingOptions, @@ -24,18 +54,18 @@ export const convertLoggingOptionsToCDK = ( loggingOptions.level, ); } - const retention = new LogRetentionConverter().toCDKRetentionDays( - loggingOptions.retention, - ); + const format = convertFormat(loggingOptions.format); return { level, - retention, format, }; }; +/** + * Converts format string to LoggingFormat enum + */ const convertFormat = (format: 'json' | 'text' | undefined) => { switch (format) { case undefined: From 259e15b2e4e679e2fa4cb35e22910de31584eecf Mon Sep 17 00:00:00 2001 From: Nico Steffens Date: Fri, 22 Aug 2025 18:27:14 +0200 Subject: [PATCH 2/2] Add changeset for log retention updates --- .changeset/silver-turkeys-argue.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/silver-turkeys-argue.md diff --git a/.changeset/silver-turkeys-argue.md b/.changeset/silver-turkeys-argue.md new file mode 100644 index 00000000000..5aad2cd778d --- /dev/null +++ b/.changeset/silver-turkeys-argue.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-function': patch +--- + +Update lambda logging configuration to use non-deprecated AWS CDK properties