Skip to content

Commit 71c492a

Browse files
authored
chore(toolkit): introduce cloudwatchlogsmonitor to deploy and watch (#33219)
### Issue # (if applicable) Closes #<issue number here>. ### Reason for this change ### Description of changes ### Describe any new or updated permissions being added ### Description of how you validated changes ### Checklist - [ ] 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*
1 parent a486bbc commit 71c492a

File tree

9 files changed

+88
-34
lines changed

9 files changed

+88
-34
lines changed

packages/@aws-cdk/toolkit/lib/actions/deploy/index.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,14 @@ export interface BaseDeployOptions {
173173
* @default 1
174174
*/
175175
readonly concurrency?: number;
176+
177+
/**
178+
* Whether to send logs from all CloudWatch log groups in the template
179+
* to the IoHost
180+
*
181+
* @default - false
182+
*/
183+
readonly traceLogs?: boolean;
176184
}
177185

178186
export interface DeployOptions extends BaseDeployOptions {
@@ -205,14 +213,6 @@ export interface DeployOptions extends BaseDeployOptions {
205213
*/
206214
readonly outputsFile?: string;
207215

208-
/**
209-
* Whether to show logs from all CloudWatch log groups in the template
210-
* locally in the users terminal
211-
*
212-
* @default - false
213-
*/
214-
readonly traceLogs?: boolean;
215-
216216
/**
217217
* Build/publish assets for a single stack in parallel
218218
*

packages/@aws-cdk/toolkit/lib/actions/deploy/private/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { DeployOptions } from '..';
2+
import { CloudWatchLogEventMonitor } from '../../../api/aws-cdk';
23

34
export * from './helpers';
45

@@ -14,4 +15,12 @@ export interface ExtendedDeployOptions extends DeployOptions {
1415
* @default - nothing extra is appended to the User-Agent header
1516
*/
1617
readonly extraUserAgent?: string;
18+
19+
/**
20+
* Allows adding CloudWatch log groups to the log monitor via
21+
* cloudWatchLogMonitor.setLogGroups();
22+
*
23+
* @default - not monitoring CloudWatch logs
24+
*/
25+
readonly cloudWatchLogMonitor?: CloudWatchLogEventMonitor;
1726
}

packages/@aws-cdk/toolkit/lib/actions/watch/index.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
import type { BaseDeployOptions, HotswapMode } from '../deploy';
22

33
export interface WatchOptions extends BaseDeployOptions {
4-
/**
5-
* Whether to show CloudWatch logs for hotswapped resources
6-
* locally in the users terminal
7-
*
8-
* @default - false
9-
*/
10-
readonly traceLogs?: boolean;
11-
124
/**
135
* The extra string to append to the User-Agent header when performing AWS SDK calls.
146
*

packages/@aws-cdk/toolkit/lib/api/aws-cdk.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export { WorkGraph } from '../../../../aws-cdk/lib/util/work-graph';
2828
export type { Concurrency } from '../../../../aws-cdk/lib/util/work-graph';
2929
export { WorkGraphBuilder } from '../../../../aws-cdk/lib/util/work-graph-builder';
3030
export type { AssetBuildNode, AssetPublishNode, StackNode } from '../../../../aws-cdk/lib/util/work-graph-types';
31+
export { CloudWatchLogEventMonitor } from '../../../../aws-cdk/lib/api/logs/logs-monitor';
32+
export { findCloudWatchLogGroups } from '../../../../aws-cdk/lib/api/logs/find-cloudwatch-logs';
3133
export { HotswapPropertyOverrides, EcsHotswapProperties } from '../../../../aws-cdk/lib/api/hotswap/common';
3234

3335
// @todo Cloud Assembly and Executable - this is a messy API right now

packages/@aws-cdk/toolkit/lib/api/io/private/codes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const CODES = {
1818
// Toolkit Info codes
1919
CDK_TOOLKIT_I0001: 'Display stack data',
2020
CDK_TOOLKIT_I0002: 'Successfully deployed stacks',
21+
CDK_TOOLKIT_I3001: 'Informs about any log groups that are traced as part of the deployment',
2122
CDK_TOOLKIT_I5001: 'Display synthesis times',
2223
CDK_TOOLKIT_I5050: 'Confirm rollback',
2324
CDK_TOOLKIT_I5060: 'Confirm deploy security sensitive changes',

packages/@aws-cdk/toolkit/lib/toolkit/toolkit.ts

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { type RollbackOptions } from '../actions/rollback';
1414
import { type SynthOptions } from '../actions/synth';
1515
import { patternsArrayForWatch, WatchOptions } from '../actions/watch';
1616
import { type SdkOptions } from '../api/aws-auth';
17-
import { DEFAULT_TOOLKIT_STACK_NAME, SdkProvider, SuccessfulDeployStackResult, StackCollection, Deployments, HotswapMode, StackActivityProgress, ResourceMigrator, obscureTemplate, serializeStructure, tagsForStack, CliIoHost, validateSnsTopicArn, Concurrency, WorkGraphBuilder, AssetBuildNode, AssetPublishNode, StackNode, formatErrorMessage } from '../api/aws-cdk';
17+
import { DEFAULT_TOOLKIT_STACK_NAME, SdkProvider, SuccessfulDeployStackResult, StackCollection, Deployments, HotswapMode, StackActivityProgress, ResourceMigrator, obscureTemplate, serializeStructure, tagsForStack, CliIoHost, validateSnsTopicArn, Concurrency, WorkGraphBuilder, AssetBuildNode, AssetPublishNode, StackNode, formatErrorMessage, CloudWatchLogEventMonitor, findCloudWatchLogGroups } from '../api/aws-cdk';
1818
import { CachedCloudAssemblySource, IdentityCloudAssemblySource, StackAssembly, ICloudAssemblySource, StackSelectionStrategy } from '../api/cloud-assembly';
1919
import { ALL_STACKS, CloudAssemblySourceBuilder } from '../api/cloud-assembly/private';
2020
import { ToolkitError } from '../api/errors';
@@ -447,15 +447,17 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
447447
[`❌ ${chalk.bold(stack.stackName)} failed:`, ...(e.name ? [`${e.name}:`] : []), e.message].join(' '),
448448
);
449449
} finally {
450-
// @todo
451-
// if (options.cloudWatchLogMonitor) {
452-
// const foundLogGroupsResult = await findCloudWatchLogGroups(await this.sdkProvider('deploy'), stack);
453-
// options.cloudWatchLogMonitor.addLogGroups(
454-
// foundLogGroupsResult.env,
455-
// foundLogGroupsResult.sdk,
456-
// foundLogGroupsResult.logGroupNames,
457-
// );
458-
// }
450+
if (options.traceLogs) {
451+
// deploy calls that originate from watch will come with their own cloudWatchLogMonitor
452+
const cloudWatchLogMonitor = options.cloudWatchLogMonitor ?? new CloudWatchLogEventMonitor();
453+
const foundLogGroupsResult = await findCloudWatchLogGroups(await this.sdkProvider('deploy'), stack);
454+
cloudWatchLogMonitor.addLogGroups(
455+
foundLogGroupsResult.env,
456+
foundLogGroupsResult.sdk,
457+
foundLogGroupsResult.logGroupNames,
458+
);
459+
await ioHost.notify(info(`The following log groups are added: ${foundLogGroupsResult.logGroupNames}`, 'CDK_TOOLKIT_I3001'));
460+
}
459461

460462
// If an outputs file has been specified, create the file path and write stack outputs to it once.
461463
// Outputs are written after all stacks have been deployed. If a stack deployment fails,
@@ -559,13 +561,12 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
559561
// -------------- -------- 'cdk deploy' done -------------- 'cdk deploy' done --------------
560562
let latch: 'pre-ready' | 'open' | 'deploying' | 'queued' = 'pre-ready';
561563

562-
// @todo
563-
// const cloudWatchLogMonitor = options.traceLogs ? new CloudWatchLogEventMonitor() : undefined;
564+
const cloudWatchLogMonitor = options.traceLogs ? new CloudWatchLogEventMonitor() : undefined;
564565
const deployAndWatch = async () => {
565566
latch = 'deploying';
566-
// cloudWatchLogMonitor?.deactivate();
567+
cloudWatchLogMonitor?.deactivate();
567568

568-
await this.invokeDeployFromWatch(assembly, options);
569+
await this.invokeDeployFromWatch(assembly, options, cloudWatchLogMonitor);
569570

570571
// If latch is still 'deploying' after the 'await', that's fine,
571572
// but if it's 'queued', that means we need to deploy again
@@ -574,10 +575,10 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
574575
// and thinks the above 'while' condition is always 'false' without the cast
575576
latch = 'deploying';
576577
await ioHost.notify(info("Detected file changes during deployment. Invoking 'cdk deploy' again"));
577-
await this.invokeDeployFromWatch(assembly, options);
578+
await this.invokeDeployFromWatch(assembly, options, cloudWatchLogMonitor);
578579
}
579580
latch = 'open';
580-
// cloudWatchLogMonitor?.activate();
581+
cloudWatchLogMonitor?.activate();
581582
};
582583

583584
chokidar
@@ -760,13 +761,14 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
760761
private async invokeDeployFromWatch(
761762
assembly: StackAssembly,
762763
options: WatchOptions,
764+
cloudWatchLogMonitor?: CloudWatchLogEventMonitor,
763765
): Promise<void> {
764766
// watch defaults hotswap to enabled
765767
const hotswap = options.hotswap ?? HotswapMode.HOTSWAP_ONLY;
766768
const deployOptions: ExtendedDeployOptions = {
767769
...options,
768770
requireApproval: RequireApproval.NEVER,
769-
// cloudWatchLogMonitor,
771+
cloudWatchLogMonitor,
770772
hotswap,
771773
extraUserAgent: `cdk-watch/hotswap-${hotswap === HotswapMode.FULL_DEPLOYMENT ? 'off' : 'on'}`,
772774
};

packages/@aws-cdk/toolkit/test/actions/deploy.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
let mockFindCloudWatchLogGroups = jest.fn();
2+
13
import { RequireApproval, StackParameters } from '../../lib';
24
import { Toolkit } from '../../lib/toolkit';
35
import { builderFixture, TestIoHost } from '../_helpers';
6+
import { MockSdk } from '../util/aws-cdk';
47

8+
const sdk = new MockSdk();
59
const ioHost = new TestIoHost();
610
const toolkit = new Toolkit({ ioHost });
711
const rollbackSpy = jest.spyOn(toolkit as any, '_rollback').mockResolvedValue({});
@@ -22,13 +26,19 @@ jest.mock('../../lib/api/aws-cdk', () => {
2226
isSingleAssetPublished: jest.fn().mockResolvedValue(true),
2327
readCurrentTemplate: jest.fn().mockResolvedValue({ Resources: {} }),
2428
})),
29+
findCloudWatchLogGroups: mockFindCloudWatchLogGroups,
2530
};
2631
});
2732

2833
beforeEach(() => {
2934
ioHost.notifySpy.mockClear();
3035
ioHost.requestSpy.mockClear();
3136
jest.clearAllMocks();
37+
mockFindCloudWatchLogGroups.mockReturnValue({
38+
env: { name: 'Z', account: 'X', region: 'Y' },
39+
sdk,
40+
logGroupNames: ['/aws/lambda/lambda-function-name'],
41+
});
3242
});
3343

3444
describe('deploy', () => {
@@ -127,6 +137,22 @@ describe('deploy', () => {
127137
successfulDeployment();
128138
});
129139

140+
test('can trace logs', async () => {
141+
// WHEN
142+
const cx = await builderFixture(toolkit, 'stack-with-role');
143+
await toolkit.deploy(cx, {
144+
traceLogs: true,
145+
});
146+
147+
// THEN
148+
expect(ioHost.notifySpy).toHaveBeenCalledWith(expect.objectContaining({
149+
action: 'deploy',
150+
level: 'info',
151+
code: 'CDK_TOOLKIT_I3001',
152+
message: expect.stringContaining('The following log groups are added: /aws/lambda/lambda-function-name'),
153+
}));
154+
});
155+
130156
test('non sns notification arn results in error', async () => {
131157
// WHEN
132158
const arn = 'arn:aws:sqs:us-east-1:1111111111:resource';

packages/@aws-cdk/toolkit/test/actions/watch.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,20 +128,39 @@ describe('watch', () => {
128128
}));
129129
});
130130

131+
test('can trace logs', async () => {
132+
// GIVEN
133+
const cx = await builderFixture(toolkit, 'stack-with-role');
134+
ioHost.level = 'debug';
135+
await toolkit.watch(cx, {
136+
include: [],
137+
traceLogs: true,
138+
});
139+
140+
// WHEN
141+
await fakeChokidarWatcherOn.readyCallback();
142+
143+
// THEN
144+
expect(deploySpy).toHaveBeenCalledWith(expect.anything(), 'watch', expect.objectContaining({
145+
cloudWatchLogMonitor: expect.anything(), // Not undefined
146+
}));
147+
});
148+
131149
describe.each([
132150
[HotswapMode.FALL_BACK, 'on'],
133151
[HotswapMode.HOTSWAP_ONLY, 'on'],
134152
[HotswapMode.FULL_DEPLOYMENT, 'off'],
135153
])('%p mode', (hotswapMode, userAgent) => {
136154
test('passes through the correct hotswap mode to deployStack()', async () => {
137-
// WHEN
155+
// GIVEN
138156
const cx = await builderFixture(toolkit, 'stack-with-role');
139157
ioHost.level = 'warn';
140158
await toolkit.watch(cx, {
141159
include: [],
142160
hotswap: hotswapMode,
143161
});
144162

163+
// WHEN
145164
await fakeChokidarWatcherOn.readyCallback();
146165

147166
// THEN
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/* eslint-disable import/no-restricted-paths */
2+
3+
export { MockSdk } from '../../../../aws-cdk/test/util/mock-sdk';

0 commit comments

Comments
 (0)