Skip to content

Commit

Permalink
chore(cli): use CliIoHost as logger everywhere (#32708)
Browse files Browse the repository at this point in the history
### Related to #32346 

### Reason for this change

Capture all logs across the CLI as IoMessages so that when we allow
customers to use the CDK Core functionality programmatically, these logs
are delivered to the customers as messages and now just swallowed across
the CLI.

### Description of changes

Prevented access to `log()` and `formatLogMessage()`. Now the only ways
to log something properly are either through the exported log functions
(which is how everyone was doing it anyway), or through
`CliIoHost.notify()` directly.

CliIoHost is now exposed as a global singleton which is currently only
directly exclusively used in `logging.ts` but is effectively used
everywhere as all logging functions inevitably call `CliIoHost.notify()`

All logging functions now optionally support the following input types
```ts
error(`operation failed: ${e}`)                                 // infers default error code `TOOLKIT_0000`
error('operation failed: %s', e)                                // infers default error code `TOOLKIT_0000`
error({ message: 'operation failed', code: 'SDK_0001' })        // specifies error code `SDK_0001`
error({ message: 'operation failed: %s', code: 'SDK_0001' }, e) // specifies error code `SDK_0001`
```
and everything is now translated into an `IoMessage` and calls
`CliIoHost.notify()` from these logging functions. Nothing currently
specifies any message code so it's all the generic _0000, _1000, _2000

### Description of how you validated changes

added and updated unit tests


### Checklist
- [x] My code adheres to the [CONTRIBUTING
GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and
[DESIGN
GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md)

----

*By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache-2.0 license*

---------

Co-authored-by: Kaizen Conroy <[email protected]>
Co-authored-by: Momo Kornher <[email protected]>
  • Loading branch information
3 people authored Jan 14, 2025
1 parent fb2b229 commit ef135ef
Show file tree
Hide file tree
Showing 36 changed files with 983 additions and 795 deletions.
7 changes: 3 additions & 4 deletions packages/aws-cdk/lib/api/aws-auth/credential-plugins.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { inspect } from 'util';
import { inspect, format } from 'util';
import type { CredentialProviderSource, ForReading, ForWriting, PluginProviderResult, SDKv2CompatibleCredentials, SDKv3CompatibleCredentialProvider, SDKv3CompatibleCredentials } from '@aws-cdk/cli-plugin-contract';
import type { AwsCredentialIdentity, AwsCredentialIdentityProvider } from '@smithy/types';
import { credentialsAboutToExpire, makeCachingProvider } from './provider-caching';
import { debug, warning } from '../../logging';
import { debug, warning, info } from '../../logging';
import { AuthenticationError } from '../../toolkit/error';
import { formatErrorMessage } from '../../util/error';
import { Mode } from '../plugin/mode';
Expand Down Expand Up @@ -151,8 +151,7 @@ function v3ProviderFromV2Credentials(x: SDKv2CompatibleCredentials): AwsCredenti

function refreshFromPluginProvider(current: AwsCredentialIdentity, producer: () => Promise<PluginProviderResult>): AwsCredentialIdentityProvider {
return async () => {
// eslint-disable-next-line no-console
console.error(current, Date.now());
info(format(current), Date.now());
if (credentialsAboutToExpire(current)) {
const newCreds = await producer();
if (!isV3Credentials(newCreds)) {
Expand Down
8 changes: 4 additions & 4 deletions packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as cxapi from '@aws-cdk/cx-api';
import * as chalk from 'chalk';
import { minimatch } from 'minimatch';
import * as semver from 'semver';
import { error, print, warning } from '../../logging';
import { error, info, warning } from '../../logging';
import { ToolkitError } from '../../toolkit/error';
import { flatten } from '../../util';

Expand Down Expand Up @@ -264,7 +264,7 @@ export class StackCollection {
printMessage(error, 'Error', message.id, message.entry);
break;
case cxapi.SynthesisMessageLevel.INFO:
printMessage(print, 'Info', message.id, message.entry);
printMessage(info, 'Info', message.id, message.entry);
break;
}
}
Expand Down Expand Up @@ -346,7 +346,7 @@ function includeDownstreamStacks(
} while (madeProgress);

if (added.length > 0) {
print('Including depending stacks: %s', chalk.bold(added.join(', ')));
info('Including depending stacks: %s', chalk.bold(added.join(', ')));
}
}

Expand Down Expand Up @@ -376,7 +376,7 @@ function includeUpstreamStacks(
}

if (added.length > 0) {
print('Including dependency stacks: %s', chalk.bold(added.join(', ')));
info('Including dependency stacks: %s', chalk.bold(added.join(', ')));
}
}

Expand Down
16 changes: 8 additions & 8 deletions packages/aws-cdk/lib/api/deploy-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { CfnEvaluationException } from './evaluate-cloudformation-template';
import { HotswapMode, HotswapPropertyOverrides, ICON } from './hotswap/common';
import { tryHotswapDeployment } from './hotswap-deployments';
import { addMetadataAssetsToManifest } from '../assets';
import { debug, print, warning } from '../logging';
import { debug, info, warning } from '../logging';
import {
changeSetHasNoChanges,
CloudFormationStack,
Expand Down Expand Up @@ -333,7 +333,7 @@ export async function deployStack(options: DeployStackOptions): Promise<DeploySt
// if we can skip deployment and we are performing a hotswap, let the user know
// that no hotswap deployment happened
if (hotswapMode !== HotswapMode.FULL_DEPLOYMENT) {
print(
info(
`\n ${ICON} %s\n`,
chalk.bold('hotswap deployment skipped - no changes were detected (use --force to override)'),
);
Expand Down Expand Up @@ -379,22 +379,22 @@ export async function deployStack(options: DeployStackOptions): Promise<DeploySt
if (hotswapDeploymentResult) {
return hotswapDeploymentResult;
}
print(
info(
'Could not perform a hotswap deployment, as the stack %s contains non-Asset changes',
stackArtifact.displayName,
);
} catch (e) {
if (!(e instanceof CfnEvaluationException)) {
throw e;
}
print(
info(
'Could not perform a hotswap deployment, because the CloudFormation template could not be resolved: %s',
formatErrorMessage(e),
);
}

if (hotswapMode === HotswapMode.FALL_BACK) {
print('Falling back to doing a full deployment');
info('Falling back to doing a full deployment');
options.sdk.appendCustomUserAgent('cdk-hotswap/fallback');
} else {
return {
Expand Down Expand Up @@ -505,7 +505,7 @@ class FullCloudFormationDeployment {
}

if (!execute) {
print(
info(
'Changeset %s created and waiting in review for manual execution (--no-execute)',
changeSetDescription.ChangeSetId,
);
Expand Down Expand Up @@ -538,7 +538,7 @@ class FullCloudFormationDeployment {
await this.cleanupOldChangeset(changeSetName);

debug(`Attempting to create ChangeSet with name ${changeSetName} to ${this.verb} stack ${this.stackName}`);
print('%s: creating CloudFormation changeset...', chalk.bold(this.stackName));
info('%s: creating CloudFormation changeset...', chalk.bold(this.stackName));
const changeSet = await this.cfn.createChangeSet({
StackName: this.stackName,
ChangeSetName: changeSetName,
Expand Down Expand Up @@ -609,7 +609,7 @@ class FullCloudFormationDeployment {
}

private async directDeployment(): Promise<SuccessfulDeployStackResult> {
print('%s: %s stack...', chalk.bold(this.stackName), this.update ? 'updating' : 'creating');
info('%s: %s stack...', chalk.bold(this.stackName), this.update ? 'updating' : 'creating');

const startTime = new Date();

Expand Down
1 change: 0 additions & 1 deletion packages/aws-cdk/lib/api/deployments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,6 @@ export class Deployments {

// No need to validate anymore, we already did that during build
const publisher = this.cachedPublisher(assetManifest, stackEnv, options.stackName);
// eslint-disable-next-line no-console
await publisher.publishEntry(asset, { allowCrossAccount: await this.allowCrossAccountAssetPublishingForEnv(options.stack) });
if (publisher.hasFailures) {
throw new Error(`Failed to publish asset ${asset.id}`);
Expand Down
18 changes: 9 additions & 9 deletions packages/aws-cdk/lib/api/garbage-collection/garbage-collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ImageIdentifier } from '@aws-sdk/client-ecr';
import { Tag } from '@aws-sdk/client-s3';
import * as chalk from 'chalk';
import * as promptly from 'promptly';
import { debug, print } from '../../logging';
import { debug, info } from '../../logging';
import { IECRClient, IS3Client, SDK, SdkProvider } from '../aws-auth';
import { DEFAULT_TOOLKIT_STACK_NAME, ToolkitInfo } from '../toolkit-info';
import { ProgressPrinter } from './progress-printer';
Expand Down Expand Up @@ -526,7 +526,7 @@ export class GarbageCollector {
printer.reportDeletedAsset(deletables.slice(0, deletedCount));
}
} catch (err) {
print(chalk.red(`Error deleting images: ${err}`));
info(chalk.red(`Error deleting images: ${err}`));
}
}

Expand Down Expand Up @@ -559,23 +559,23 @@ export class GarbageCollector {
printer.reportDeletedAsset(deletables.slice(0, deletedCount));
}
} catch (err) {
print(chalk.red(`Error deleting objects: ${err}`));
info(chalk.red(`Error deleting objects: ${err}`));
}
}

private async bootstrapBucketName(sdk: SDK, bootstrapStackName: string): Promise<string> {
const info = await ToolkitInfo.lookup(this.props.resolvedEnvironment, sdk, bootstrapStackName);
return info.bucketName;
const toolkitInfo = await ToolkitInfo.lookup(this.props.resolvedEnvironment, sdk, bootstrapStackName);
return toolkitInfo.bucketName;
}

private async bootstrapRepositoryName(sdk: SDK, bootstrapStackName: string): Promise<string> {
const info = await ToolkitInfo.lookup(this.props.resolvedEnvironment, sdk, bootstrapStackName);
return info.repositoryName;
const toolkitInfo = await ToolkitInfo.lookup(this.props.resolvedEnvironment, sdk, bootstrapStackName);
return toolkitInfo.repositoryName;
}

private async bootstrapQualifier(sdk: SDK, bootstrapStackName: string): Promise<string | undefined> {
const info = await ToolkitInfo.lookup(this.props.resolvedEnvironment, sdk, bootstrapStackName);
return info.bootstrapStack.parameters.Qualifier;
const toolkitInfo = await ToolkitInfo.lookup(this.props.resolvedEnvironment, sdk, bootstrapStackName);
return toolkitInfo.bootstrapStack.parameters.Qualifier;
}

private async numObjectsInBucket(s3: IS3Client, bucket: string): Promise<number> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as chalk from 'chalk';
import { GcAsset as GCAsset } from './garbage-collector';
import { print } from '../../logging';
import { info } from '../../logging';

export class ProgressPrinter {
private totalAssets: number;
Expand Down Expand Up @@ -68,9 +68,9 @@ export class ProgressPrinter {
const percentage = ((this.assetsScanned / this.totalAssets) * 100).toFixed(2);
// print in MiB until we hit at least 1 GiB of data tagged/deleted
if (Math.max(this.taggedAssetsSizeMb, this.deletedAssetsSizeMb) >= 1000) {
print(chalk.green(`[${percentage}%] ${this.assetsScanned} files scanned: ${this.taggedAsset} assets (${(this.taggedAssetsSizeMb / 1000).toFixed(2)} GiB) tagged, ${this.deletedAssets} assets (${(this.deletedAssetsSizeMb / 1000).toFixed(2)} GiB) deleted.`));
info(chalk.green(`[${percentage}%] ${this.assetsScanned} files scanned: ${this.taggedAsset} assets (${(this.taggedAssetsSizeMb / 1000).toFixed(2)} GiB) tagged, ${this.deletedAssets} assets (${(this.deletedAssetsSizeMb / 1000).toFixed(2)} GiB) deleted.`));
} else {
print(chalk.green(`[${percentage}%] ${this.assetsScanned} files scanned: ${this.taggedAsset} assets (${this.taggedAssetsSizeMb.toFixed(2)} MiB) tagged, ${this.deletedAssets} assets (${this.deletedAssetsSizeMb.toFixed(2)} MiB) deleted.`));
info(chalk.green(`[${percentage}%] ${this.assetsScanned} files scanned: ${this.taggedAsset} assets (${this.taggedAssetsSizeMb.toFixed(2)} MiB) tagged, ${this.deletedAssets} assets (${this.deletedAssetsSizeMb.toFixed(2)} MiB) deleted.`));
}
}
}
18 changes: 9 additions & 9 deletions packages/aws-cdk/lib/api/hotswap-deployments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as chalk from 'chalk';
import type { SDK, SdkProvider } from './aws-auth';
import type { SuccessfulDeployStackResult } from './deploy-stack';
import { EvaluateCloudFormationTemplate } from './evaluate-cloudformation-template';
import { print } from '../logging';
import { info } from '../logging';
import { isHotswappableAppSyncChange } from './hotswap/appsync-mapping-templates';
import { isHotswappableCodeBuildProjectChange } from './hotswap/code-build-projects';
import {
Expand Down Expand Up @@ -400,7 +400,7 @@ function isCandidateForHotswapping(

async function applyAllHotswappableChanges(sdk: SDK, hotswappableChanges: HotswappableChange[]): Promise<void[]> {
if (hotswappableChanges.length > 0) {
print(`\n${ICON} hotswapping resources:`);
info(`\n${ICON} hotswapping resources:`);
}
const limit = pLimit(10);
// eslint-disable-next-line @cdklabs/promiseall-no-unbounded-parallelism
Expand All @@ -415,7 +415,7 @@ async function applyHotswappableChange(sdk: SDK, hotswapOperation: HotswappableC
sdk.appendCustomUserAgent(customUserAgent);

for (const name of hotswapOperation.resourceNames) {
print(` ${ICON} %s`, chalk.bold(name));
info(` ${ICON} %s`, chalk.bold(name));
}

// if the SDK call fails, an error will be thrown by the SDK
Expand All @@ -436,7 +436,7 @@ async function applyHotswappableChange(sdk: SDK, hotswapOperation: HotswappableC
}

for (const name of hotswapOperation.resourceNames) {
print(`${ICON} %s %s`, chalk.bold(name), chalk.green('hotswapped!'));
info(`${ICON} %s %s`, chalk.bold(name), chalk.green('hotswapped!'));
}

sdk.removeCustomUserAgent(customUserAgent);
Expand All @@ -461,33 +461,33 @@ function logNonHotswappableChanges(nonHotswappableChanges: NonHotswappableChange
}
}
if (hotswapMode === HotswapMode.HOTSWAP_ONLY) {
print(
info(
'\n%s %s',
chalk.red('⚠️'),
chalk.red(
'The following non-hotswappable changes were found. To reconcile these using CloudFormation, specify --hotswap-fallback',
),
);
} else {
print('\n%s %s', chalk.red('⚠️'), chalk.red('The following non-hotswappable changes were found:'));
info('\n%s %s', chalk.red('⚠️'), chalk.red('The following non-hotswappable changes were found:'));
}

for (const change of nonHotswappableChanges) {
change.rejectedChanges.length > 0
? print(
? info(
' logicalID: %s, type: %s, rejected changes: %s, reason: %s',
chalk.bold(change.logicalId),
chalk.bold(change.resourceType),
chalk.bold(change.rejectedChanges),
chalk.red(change.reason),
)
: print(
: info(
' logicalID: %s, type: %s, reason: %s',
chalk.bold(change.logicalId),
chalk.bold(change.resourceType),
chalk.red(change.reason),
);
}

print(''); // newline
info(''); // newline
}
4 changes: 2 additions & 2 deletions packages/aws-cdk/lib/api/logs/logs-monitor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as util from 'util';
import * as cxapi from '@aws-cdk/cx-api';
import * as chalk from 'chalk';
import { print, error } from '../../logging';
import { info, error } from '../../logging';
import { flatten } from '../../util/arrays';
import type { SDK } from '../aws-auth';

Expand Down Expand Up @@ -162,7 +162,7 @@ export class CloudWatchLogEventMonitor {
* Print out a cloudwatch event
*/
private print(event: CloudWatchLogEvent): void {
print(
info(
util.format(
'[%s] %s %s',
chalk.blue(event.logGroupName),
Expand Down
2 changes: 1 addition & 1 deletion packages/aws-cdk/lib/api/plugin/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { inspect } from 'util';
import type { CredentialProviderSource, IPluginHost, Plugin } from '@aws-cdk/cli-plugin-contract';
import * as chalk from 'chalk';

import * as chalk from 'chalk';
import { type ContextProviderPlugin, isContextProviderPlugin } from './context-provider-plugin';
import { error } from '../../logging';
import { ToolkitError } from '../../toolkit/error';
Expand Down
1 change: 0 additions & 1 deletion packages/aws-cdk/lib/api/util/cloudformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,6 @@ export async function createDiffChangeSet(
// This causes CreateChangeSet to fail with `Template Error: Fn::Equals cannot be partially collapsed`.
for (const resource of Object.values(options.stack.template.Resources ?? {})) {
if ((resource as any).Type === 'AWS::CloudFormation::Stack') {
// eslint-disable-next-line no-console
debug('This stack contains one or more nested stacks, falling back to template-only diff...');

return undefined;
Expand Down
Loading

0 comments on commit ef135ef

Please sign in to comment.