diff --git a/.changeset/silver-turkeys-argue.md b/.changeset/silver-turkeys-argue.md new file mode 100644 index 0000000000..5aad2cd778 --- /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 diff --git a/packages/backend-function/src/factory.test.ts b/packages/backend-function/src/factory.test.ts index 6f1ddd6bb5..6840869597 100644 --- a/packages/backend-function/src/factory.test.ts +++ b/packages/backend-function/src/factory.test.ts @@ -654,7 +654,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: { @@ -667,15 +667,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: { @@ -684,6 +683,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 b1bd96d474..795e519e83 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 { convertFunctionSchedulesToScheduleExpressions } from './schedule_parser.js'; import { ProvidedFunctionFactory, @@ -599,6 +602,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, @@ -615,7 +625,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 4d6abec958..0203098f7e 100644 --- a/packages/backend-function/src/logging_options_parser.test.ts +++ b/packages/backend-function/src/logging_options_parser.test.ts @@ -4,22 +4,24 @@ 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'; +import { Template } from 'aws-cdk-lib/assertions'; -type TestCase = { +type ConversionTestCase = { input: FunctionLoggingOptions; expectedOutput: CDKLoggingOptions; }; -const testCases: Array = [ +const conversionTestCases: Array = [ { input: {}, expectedOutput: { format: undefined, level: undefined, - retention: undefined, }, }, { @@ -29,7 +31,6 @@ const testCases: Array = [ }, expectedOutput: { format: LoggingFormat.TEXT, - retention: RetentionDays.THIRTEEN_MONTHS, level: undefined, }, }, @@ -40,17 +41,38 @@ 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); + + // Verify the synthesized CloudFormation template + const template = Template.fromStack(stack); + + // Ensure we found exactly one log group + template.resourceCountIs('AWS::Logs::LogGroup', 1); + + // Verify retention period (13 months = 400 days) + template.hasResourceProperties('AWS::Logs::LogGroup', { + RetentionInDays: 400, + }); + }); }); diff --git a/packages/backend-function/src/logging_options_parser.ts b/packages/backend-function/src/logging_options_parser.ts index 028f7aa67d..5e2d7628f5 100644 --- a/packages/backend-function/src/logging_options_parser.ts +++ b/packages/backend-function/src/logging_options_parser.ts @@ -4,16 +4,43 @@ 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 */ export const convertLoggingOptionsToCDK = ( loggingOptions: FunctionLoggingOptions, @@ -24,18 +51,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: