Skip to content

Commit 2488620

Browse files
committed
feat(toolkit-lib): structured log monitoring events
1 parent 01acccf commit 2488620

File tree

4 files changed

+113
-37
lines changed

4 files changed

+113
-37
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
509509
} finally {
510510
if (options.traceLogs) {
511511
// deploy calls that originate from watch will come with their own cloudWatchLogMonitor
512-
const cloudWatchLogMonitor = options.cloudWatchLogMonitor ?? new CloudWatchLogEventMonitor();
512+
const cloudWatchLogMonitor = options.cloudWatchLogMonitor ?? new CloudWatchLogEventMonitor({ action, ioHost });
513513
const foundLogGroupsResult = await findCloudWatchLogGroups(await this.sdkProvider('deploy'), { ioHost, action }, stack);
514514
cloudWatchLogMonitor.addLogGroups(
515515
foundLogGroupsResult.env,
@@ -618,10 +618,10 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
618618
// -------------- -------- 'cdk deploy' done -------------- 'cdk deploy' done --------------
619619
let latch: 'pre-ready' | 'open' | 'deploying' | 'queued' = 'pre-ready';
620620

621-
const cloudWatchLogMonitor = options.traceLogs ? new CloudWatchLogEventMonitor() : undefined;
621+
const cloudWatchLogMonitor = options.traceLogs ? new CloudWatchLogEventMonitor({ ioHost, action: 'watch' }) : undefined;
622622
const deployAndWatch = async () => {
623623
latch = 'deploying';
624-
cloudWatchLogMonitor?.deactivate();
624+
await cloudWatchLogMonitor?.deactivate();
625625

626626
await this.invokeDeployFromWatch(assembly, options, cloudWatchLogMonitor);
627627

@@ -635,7 +635,7 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
635635
await this.invokeDeployFromWatch(assembly, options, cloudWatchLogMonitor);
636636
}
637637
latch = 'open';
638-
cloudWatchLogMonitor?.activate();
638+
await cloudWatchLogMonitor?.activate();
639639
};
640640

641641
chokidar

packages/aws-cdk/lib/api/logs/find-cloudwatch-logs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { CloudFormationStackArtifact, Environment } from '@aws-cdk/cx-api';
22
import type { StackResourceSummary } from '@aws-sdk/client-cloudformation';
3-
import { debug } from '../../logging';
3+
import { debug } from '../../cli/messages';
44
import { IoMessaging } from '../../toolkit/cli-io-host';
55
import { formatErrorMessage } from '../../util';
66
import type { SDK, SdkProvider } from '../aws-auth';
@@ -47,7 +47,7 @@ export async function findCloudWatchLogGroups(
4747
try {
4848
sdk = (await new EnvironmentAccess(sdkProvider, DEFAULT_TOOLKIT_STACK_NAME, msg).accessStackForLookup(stackArtifact)).sdk;
4949
} catch (e: any) {
50-
debug(`Failed to access SDK environment: ${formatErrorMessage(e)}`);
50+
await msg.ioHost.notify(debug(msg.action, `Failed to access SDK environment: ${formatErrorMessage(e)}`));
5151
sdk = (await sdkProvider.forEnvironment(resolvedEnv, Mode.ForReading)).sdk;
5252
}
5353

packages/aws-cdk/lib/api/logs/logs-monitor.ts

Lines changed: 101 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,34 @@
11
import * as util from 'util';
22
import * as cxapi from '@aws-cdk/cx-api';
33
import * as chalk from 'chalk';
4-
import { info, error } from '../../logging';
4+
import * as uuid from 'uuid';
5+
import { debug, error, info } from '../../cli/messages';
6+
import { IoMessaging } from '../../toolkit/cli-io-host';
57
import { flatten } from '../../util';
68
import type { SDK } from '../aws-auth';
79

810
/**
9-
* After reading events from all CloudWatch log groups
10-
* how long should we wait to read more events.
11-
*
12-
* If there is some error with reading events (i.e. Throttle)
13-
* then this is also how long we wait until we try again
11+
* Payload when stack monitoring is starting or stopping for a given stack deployment.
1412
*/
15-
const SLEEP = 2_000;
13+
export interface CloudWatchLogMonitorControlEvent {
14+
/**
15+
* A unique identifier for a monitor
16+
*
17+
* Use this value to attribute events received for concurrent log monitoring.
18+
*/
19+
readonly monitor: string;
20+
21+
/**
22+
* The names of monitored log groups
23+
*/
24+
readonly logGroupNames: string[];
25+
}
1626

1727
/**
1828
* Represents a CloudWatch Log Event that will be
1929
* printed to the terminal
2030
*/
21-
interface CloudWatchLogEvent {
31+
export interface CloudWatchLogEvent {
2232
/**
2333
* The log event message
2434
*/
@@ -54,6 +64,25 @@ interface LogGroupsAccessSettings {
5464
readonly logGroupsStartTimes: { [logGroupName: string]: number };
5565
}
5666

67+
export interface CloudWatchLogEventMonitorProps {
68+
/**
69+
* The IoHost used for messaging
70+
*/
71+
readonly ioHost: IoMessaging['ioHost'];
72+
73+
/**
74+
* The current ToolkitAction
75+
*/
76+
readonly action: IoMessaging['action'];
77+
78+
/**
79+
* The time from which we start reading log messages
80+
*
81+
* @default - now
82+
*/
83+
readonly startTime?: Date;
84+
}
85+
5786
export class CloudWatchLogEventMonitor {
5887
/**
5988
* Determines which events not to display
@@ -65,18 +94,38 @@ export class CloudWatchLogEventMonitor {
6594
*/
6695
private readonly envsLogGroupsAccessSettings = new Map<string, LogGroupsAccessSettings>();
6796

68-
private active = false;
97+
/**
98+
* After reading events from all CloudWatch log groups
99+
* how long should we wait to read more events.
100+
*
101+
* If there is some error with reading events (i.e. Throttle)
102+
* then this is also how long we wait until we try again
103+
*/
104+
private readonly pollingInterval: number = 2_000;
105+
106+
private monitorId?: string;
107+
private readonly ioHost: IoMessaging['ioHost'];
108+
private readonly action: IoMessaging['action'];
69109

70-
constructor(startTime?: Date) {
71-
this.startTime = startTime?.getTime() ?? Date.now();
110+
constructor(props: CloudWatchLogEventMonitorProps) {
111+
this.startTime = props.startTime?.getTime() ?? Date.now();
112+
this.ioHost = props.ioHost;
113+
this.action = props.action;
72114
}
73115

74116
/**
75117
* resume reading/printing events
76118
*/
77-
public activate(): void {
78-
this.active = true;
79-
this.scheduleNextTick(0);
119+
public async activate(): Promise<void> {
120+
this.monitorId = uuid.v4();
121+
122+
await this.ioHost.notify(debug(this.action, 'Start monitoring log groups', '', {
123+
monitor: this.monitorId,
124+
logGroupNames: this.logGroupNames(),
125+
} as CloudWatchLogMonitorControlEvent));
126+
127+
await this.tick();
128+
this.scheduleNextTick();
80129
}
81130

82131
/**
@@ -88,9 +137,16 @@ export class CloudWatchLogEventMonitor {
88137
* Also resets the start time to be when the new deployment was triggered
89138
* and clears the list of tracked log groups
90139
*/
91-
public deactivate(): void {
92-
this.active = false;
140+
public async deactivate(): Promise<void> {
141+
const oldMonitorId = this.monitorId!;
142+
this.monitorId = undefined;
93143
this.startTime = Date.now();
144+
145+
await this.ioHost.notify(debug(this.action, 'Stopped monitoring log groups', '', {
146+
monitor: oldMonitorId,
147+
logGroupNames: this.logGroupNames(),
148+
} as CloudWatchLogMonitorControlEvent));
149+
94150
this.envsLogGroupsAccessSettings.clear();
95151
}
96152

@@ -119,27 +175,41 @@ export class CloudWatchLogEventMonitor {
119175
});
120176
}
121177

122-
private scheduleNextTick(sleep: number): void {
123-
setTimeout(() => void this.tick(), sleep);
178+
private logGroupNames(): string[] {
179+
return Array.from(this.envsLogGroupsAccessSettings.values()).flatMap((settings) => Object.keys(settings.logGroupsStartTimes));
180+
}
181+
182+
private scheduleNextTick(): void {
183+
if (!this.monitorId) {
184+
return;
185+
}
186+
187+
setTimeout(() => void this.tick(), this.pollingInterval);
124188
}
125189

126190
private async tick(): Promise<void> {
127191
// excluding from codecoverage because this
128192
// doesn't always run (depends on timing)
129-
/* istanbul ignore next */
130-
if (!this.active) {
193+
/* c8 ignore next */
194+
if (!this.monitorId) {
131195
return;
132196
}
197+
133198
try {
134199
const events = flatten(await this.readNewEvents());
135-
events.forEach((event) => {
136-
this.print(event);
137-
});
200+
for (const event of events) {
201+
await this.print(event);
202+
}
203+
204+
// We might have been stop()ped while the network call was in progress.
205+
if (!this.monitorId) {
206+
return;
207+
}
138208
} catch (e) {
139-
error('Error occurred while monitoring logs: %s', e);
209+
await this.ioHost.notify(error(this.action, 'Error occurred while monitoring logs: %s', { error: e }));
140210
}
141211

142-
this.scheduleNextTick(SLEEP);
212+
this.scheduleNextTick();
143213
}
144214

145215
/**
@@ -161,15 +231,18 @@ export class CloudWatchLogEventMonitor {
161231
/**
162232
* Print out a cloudwatch event
163233
*/
164-
private print(event: CloudWatchLogEvent): void {
165-
info(
234+
private async print(event: CloudWatchLogEvent): Promise<void> {
235+
await this.ioHost.notify(info(
236+
this.action,
166237
util.format(
167238
'[%s] %s %s',
168239
chalk.blue(event.logGroupName),
169240
chalk.yellow(event.timestamp.toLocaleTimeString()),
170241
event.message.trim(),
171242
),
172-
);
243+
'',
244+
event,
245+
));
173246
}
174247

175248
/**

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -698,10 +698,13 @@ export class CdkToolkit {
698698
// -------------- -------- 'cdk deploy' done -------------- 'cdk deploy' done --------------
699699
let latch: 'pre-ready' | 'open' | 'deploying' | 'queued' = 'pre-ready';
700700

701-
const cloudWatchLogMonitor = options.traceLogs ? new CloudWatchLogEventMonitor() : undefined;
701+
const cloudWatchLogMonitor = options.traceLogs ? new CloudWatchLogEventMonitor({
702+
action: 'watch',
703+
ioHost: this.ioHost,
704+
}) : undefined;
702705
const deployAndWatch = async () => {
703706
latch = 'deploying';
704-
cloudWatchLogMonitor?.deactivate();
707+
await cloudWatchLogMonitor?.deactivate();
705708

706709
await this.invokeDeployFromWatch(options, cloudWatchLogMonitor);
707710

@@ -715,7 +718,7 @@ export class CdkToolkit {
715718
await this.invokeDeployFromWatch(options, cloudWatchLogMonitor);
716719
}
717720
latch = 'open';
718-
cloudWatchLogMonitor?.activate();
721+
await cloudWatchLogMonitor?.activate();
719722
};
720723

721724
chokidar

0 commit comments

Comments
 (0)