From 33c7ac81d525b23fe766d65aa6ff523335e0fe09 Mon Sep 17 00:00:00 2001 From: Francis Date: Wed, 3 Apr 2024 19:29:56 -0700 Subject: [PATCH] logging Signed-off-by: Francis --- .../aws-sdk-v3-handler.ts | 27 ++++-- .../aws-custom-resource-handler/shared.ts | 8 +- .../aws-custom-resource.ts | 12 +++ .../lib/aws-custom-resource/logging.ts | 93 +++++++++++++++++++ 4 files changed, 130 insertions(+), 10 deletions(-) create mode 100644 packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/logging.ts diff --git a/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources/aws-custom-resource-handler/aws-sdk-v3-handler.ts b/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources/aws-custom-resource-handler/aws-sdk-v3-handler.ts index d2630e081fb1b..4f8adffea9cfc 100644 --- a/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources/aws-custom-resource-handler/aws-sdk-v3-handler.ts +++ b/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources/aws-custom-resource-handler/aws-sdk-v3-handler.ts @@ -34,10 +34,12 @@ function installLatestSdk(packageName: string): void { } interface AwsSdk { - [key: string]: any + [key: string]: any; } + async function loadAwsSdk( packageName: string, + logErrors: boolean, installLatestAwsSdk?: 'true' | 'false', ) { let awsSdk: AwsSdk; @@ -49,7 +51,9 @@ async function loadAwsSdk( // esbuild-disable unsupported-require-call -- not esbuildable but that's fine awsSdk = require(`/tmp/node_modules/${packageName}`); } catch (e) { - console.log(`Failed to install latest AWS SDK v3. Falling back to pre-installed version. Error: ${e}`); + if (logErrors) { + console.log(`Failed to install latest AWS SDK v3. Falling back to pre-installed version. Error: ${e}`); + } // MUST use require as dynamic import() does not support importing from directories // esbuild-disable unsupported-require-call -- not esbuildable but that's fine return require(packageName); // Fallback to pre-installed version @@ -71,6 +75,11 @@ async function loadAwsSdk( /* eslint-disable @typescript-eslint/no-require-imports, import/no-extraneous-dependencies */ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent, context: AWSLambda.Context) { + let logHandlerEvent = event.ResourceProperties.LogHandlerEvent === 'true'; + let logApiResponse = event.ResourceProperties.LogApiResponse === 'true'; + let logResponseObject = event.ResourceProperties.LogResponseObject === 'true'; + let logErrors = event.ResourceProperties.LogErrors === 'true'; + try { event.ResourceProperties.Create = decodeCall(event.ResourceProperties.Create); event.ResourceProperties.Update = decodeCall(event.ResourceProperties.Update); @@ -95,9 +104,11 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent if (call) { const apiCall = new ApiCall(call.service, call.action); - let awsSdk: AwsSdk | Promise = loadAwsSdk(apiCall.v3PackageName, event.ResourceProperties.InstallLatestAwsSdk); + let awsSdk: AwsSdk | Promise = loadAwsSdk(apiCall.v3PackageName, logErrors, event.ResourceProperties.InstallLatestAwsSdk); - console.log(JSON.stringify({ ...event, ResponseURL: '...' })); + if (logHandlerEvent) { + console.log(JSON.stringify({ ...event, ResponseURL: '...' })); + } let credentials; if (call.assumedRoleArn) { @@ -128,7 +139,9 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent flattenResponse: true, }); - console.log('API response', response); + if (logApiResponse) { + console.log('API response', response); + } flatData.apiVersion = apiCall.client.config.apiVersion; // For test purposes: check if apiVersion was correctly passed. flatData.region = await apiCall.client.config.region().catch(() => undefined); // For test purposes: check if region was correctly passed. @@ -159,9 +172,9 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent } } - await respond(event, 'SUCCESS', 'OK', physicalResourceId, data); + await respond(event, 'SUCCESS', 'OK', physicalResourceId, data, logResponseObject); } catch (e: any) { console.log(e); - await respond(event, 'FAILED', e.message || 'Internal Error', context.logStreamName, {}); + await respond(event, 'FAILED', e.message || 'Internal Error', context.logStreamName, {}, logResponseObject); } } diff --git a/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources/aws-custom-resource-handler/shared.ts b/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources/aws-custom-resource-handler/shared.ts index 2b0985caf1cb0..ae2ef77071780 100644 --- a/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources/aws-custom-resource-handler/shared.ts +++ b/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources/aws-custom-resource-handler/shared.ts @@ -43,7 +43,7 @@ export function filterKeys(object: object, pred: (key: string) => boolean) { type Event = AWSLambda.CloudFormationCustomResourceEvent -export function respond(event: Event, responseStatus: string, reason: string, physicalResourceId: string, data: any) { +export function respond(event: Event, responseStatus: string, reason: string, physicalResourceId: string, data: any, logResponseObject: boolean) { const responseBody = JSON.stringify({ Status: responseStatus, Reason: reason, @@ -55,8 +55,10 @@ export function respond(event: Event, responseStatus: string, reason: string, ph Data: data, }); - // eslint-disable-next-line no-console - console.log('Responding', responseBody); + if (logResponseObject) { + // eslint-disable-next-line no-console + console.log('Responding', responseBody); + } // eslint-disable-next-line @typescript-eslint/no-require-imports const parsedUrl = require('url').parse(event.ResponseURL); diff --git a/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts b/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts index 67651b621023e..a53a33132105f 100644 --- a/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts +++ b/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts @@ -7,6 +7,7 @@ import { Annotations } from '../../../core'; import { AwsCustomResourceSingletonFunction } from '../../../custom-resource-handlers/dist/custom-resources/aws-custom-resource-provider.generated'; import * as cxapi from '../../../cx-api'; import { awsSdkToIamAction } from '../helpers-internal/sdk-info'; +import { Logging } from './logging'; // Shared definition with packages/@aws-cdk/custom-resource-handlers/lib/custom-resources/aws-custom-resource-handler/shared.ts const PHYSICAL_RESOURCE_ID_REFERENCE = 'PHYSICAL:RESOURCEID:'; @@ -394,6 +395,13 @@ export interface AwsCustomResourceProps { * @default - the Vpc default strategy if not specified */ readonly vpcSubnets?: ec2.SubnetSelection; + + /** + * Enables logging + * + * @default Logging.on() + */ + readonly logging?: Logging; } /** @@ -497,6 +505,10 @@ export class AwsCustomResource extends Construct implements iam.IGrantable { update: props.onUpdate && this.encodeJson(props.onUpdate), delete: props.onDelete && this.encodeJson(props.onDelete), installLatestAwsSdk, + logHandlerEvent: props.logging?.logHandlerEvent ?? true, + logApiResponse: props.logging?.logApiResponse ?? true, + logRespondeObject: props.logging?.logResponseObject ?? true, + logErrors: props.logging?.logErrors ?? true, }, }); diff --git a/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/logging.ts b/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/logging.ts new file mode 100644 index 0000000000000..eaaf3f7b01a69 --- /dev/null +++ b/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/logging.ts @@ -0,0 +1,93 @@ +/** + * Properties used to initialize Logging. + */ +export interface LoggingProps { + /** + * Whether or not to log the event object received by the lambda handler. + * + * @default true + */ + readonly logHandlerEvent?: boolean; + + /** + * Whether or not to log the response returned from the API call. + * + * @default true + */ + readonly logApiResponse?: boolean; + + /** + * Whether or not to log the response object that will be returned by the lambda. + * + * @default true + */ + readonly logResponseObject?: boolean; + + /** + * Whether or not to log errors that were encountered during lambda execution. + * + * @default true + */ + readonly logErrors?: boolean; +} + +/** + * A class that represents Logging that will take place during lambda execution. + */ +export class Logging { + /** + * Enables logging of all logged data in the lambda handler. + * + * This includes the event object, the API call response, all fields in the response object + * returned by the lambda, and any errors encountered. + */ + public static on() { + return new Logging(); + } + + /** + * Enables selective logging of logged data in the lambda handler. + */ + public static selective(props: LoggingProps) { + return new Logging({ ...props }); + } + + /** + * Turns off all logging in the lambda handler. + */ + public static off() { + return new Logging({ + logHandlerEvent: false, + logApiResponse: false, + logResponseObject: false, + logErrors: false, + }); + } + + /** + * Whether or not to log the event object received by the lambda handler. + */ + public readonly logHandlerEvent: boolean; + + /** + * Whether or not to log the API call response. + */ + public readonly logApiResponse: boolean; + + /** + * Whether or not to log the response object that will be returned by the lambda. + */ + public readonly logResponseObject: boolean; + + /** + * Whether or not to log errors that were encountered during lambda execution. + */ + public readonly logErrors: boolean; + + private constructor(props: LoggingProps = {}) { + this.logHandlerEvent = props.logHandlerEvent ?? true; + this.logApiResponse = props.logApiResponse ?? true; + this.logResponseObject = props.logResponseObject ?? true; + this.logErrors = props.logErrors ?? true; + } +} \ No newline at end of file