Skip to content

Commit 1bef263

Browse files
mrgraingithub-actions
and
github-actions
authored
feat(toolkit-lib): structured log monitoring events (#218)
Moves more of the APIs to use modern IO infrastructure. --- By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license --------- Signed-off-by: github-actions <[email protected]> Co-authored-by: github-actions <[email protected]>
1 parent 84d0b54 commit 1bef263

File tree

10 files changed

+175
-86
lines changed

10 files changed

+175
-86
lines changed

packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export * from './progress';
1212
export * from './watch';
1313
export * from './stack-details';
1414
export * from './diff';
15+
export * from './logs-monitor';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
2+
/**
3+
* Payload when stack monitoring is starting or stopping for a given stack deployment.
4+
*/
5+
export interface CloudWatchLogMonitorControlEvent {
6+
/**
7+
* A unique identifier for a monitor
8+
*
9+
* Use this value to attribute events received for concurrent log monitoring.
10+
*/
11+
readonly monitor: string;
12+
13+
/**
14+
* The names of monitored log groups
15+
*/
16+
readonly logGroupNames: string[];
17+
}
18+
19+
/**
20+
* Represents a CloudWatch Log Event that will be
21+
* printed to the terminal
22+
*/
23+
export interface CloudWatchLogEvent {
24+
/**
25+
* The log event message
26+
*/
27+
readonly message: string;
28+
29+
/**
30+
* The name of the log group
31+
*/
32+
readonly logGroupName: string;
33+
34+
/**
35+
* The time at which the event occurred
36+
*/
37+
readonly timestamp: Date;
38+
}

packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/messages.ts

+21
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { MissingContext, UpdatedContext } from '../payloads/context';
66
import type { BuildAsset, DeployConfirmationRequest, PublishAsset, StackDeployProgress, SuccessfulDeployStackResult } from '../payloads/deploy';
77
import type { StackDestroy, StackDestroyProgress } from '../payloads/destroy';
88
import type { StackDetailsPayload } from '../payloads/list';
9+
import type { CloudWatchLogEvent, CloudWatchLogMonitorControlEvent } from '../payloads/logs-monitor';
910
import type { StackRollbackProgress } from '../payloads/rollback';
1011
import type { SdkTrace } from '../payloads/sdk-trace';
1112
import type { StackActivity, StackMonitoringControlEvent } from '../payloads/stack-activity';
@@ -98,6 +99,26 @@ export const IO = {
9899
code: 'CDK_TOOLKIT_I5031',
99100
description: 'Informs about any log groups that are traced as part of the deployment',
100101
}),
102+
CDK_TOOLKIT_I5032: make.debug<CloudWatchLogMonitorControlEvent>({
103+
code: 'CDK_TOOLKIT_I5032',
104+
description: 'Start monitoring log groups',
105+
interface: 'CloudWatchLogMonitorControlEvent',
106+
}),
107+
CDK_TOOLKIT_I5033: make.info<CloudWatchLogEvent>({
108+
code: 'CDK_TOOLKIT_I5033',
109+
description: 'A log event received from Cloud Watch',
110+
interface: 'CloudWatchLogEvent',
111+
}),
112+
CDK_TOOLKIT_I5034: make.debug<CloudWatchLogMonitorControlEvent>({
113+
code: 'CDK_TOOLKIT_I5034',
114+
description: 'Stop monitoring log groups',
115+
interface: 'CloudWatchLogMonitorControlEvent',
116+
}),
117+
CDK_TOOLKIT_E5035: make.error<ErrorPayload>({
118+
code: 'CDK_TOOLKIT_E5035',
119+
description: 'A log monitoring error',
120+
interface: 'ErrorPayload',
121+
}),
101122
CDK_TOOLKIT_I5050: make.confirm<ConfirmationRequest>({
102123
code: 'CDK_TOOLKIT_I5050',
103124
description: 'Confirm rollback during deployment',

packages/@aws-cdk/toolkit-lib/docs/message-registry.md

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ group: Documents
2121
| `CDK_TOOLKIT_W5021` | Empty non-existent stack, deployment is skipped | `warn` | n/a |
2222
| `CDK_TOOLKIT_W5022` | Empty existing stack, stack will be destroyed | `warn` | n/a |
2323
| `CDK_TOOLKIT_I5031` | Informs about any log groups that are traced as part of the deployment | `info` | n/a |
24+
| `CDK_TOOLKIT_I5032` | Start monitoring log groups | `debug` | {@link CloudWatchLogMonitorControlEvent} |
25+
| `CDK_TOOLKIT_I5033` | A log event received from Cloud Watch | `info` | {@link CloudWatchLogEvent} |
26+
| `CDK_TOOLKIT_I5034` | Stop monitoring log groups | `debug` | {@link CloudWatchLogMonitorControlEvent} |
27+
| `CDK_TOOLKIT_E5035` | A log monitoring error | `error` | {@link ErrorPayload} |
2428
| `CDK_TOOLKIT_I5050` | Confirm rollback during deployment | `info` | {@link ConfirmationRequest} |
2529
| `CDK_TOOLKIT_I5060` | Confirm deploy security sensitive changes | `info` | {@link DeployConfirmationRequest} |
2630
| `CDK_TOOLKIT_I5100` | Stack deploy progress | `info` | {@link StackDeployProgress} |

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
519519
} finally {
520520
if (options.traceLogs) {
521521
// deploy calls that originate from watch will come with their own cloudWatchLogMonitor
522-
const cloudWatchLogMonitor = options.cloudWatchLogMonitor ?? new CloudWatchLogEventMonitor();
522+
const cloudWatchLogMonitor = options.cloudWatchLogMonitor ?? new CloudWatchLogEventMonitor({ ioHelper });
523523
const foundLogGroupsResult = await findCloudWatchLogGroups(await this.sdkProvider('deploy'), ioHelper, stack);
524524
cloudWatchLogMonitor.addLogGroups(
525525
foundLogGroupsResult.env,
@@ -637,10 +637,10 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
637637
type LatchState = 'pre-ready' | 'open' | 'deploying' | 'queued';
638638
let latch: LatchState = 'pre-ready';
639639

640-
const cloudWatchLogMonitor = options.traceLogs ? new CloudWatchLogEventMonitor() : undefined;
640+
const cloudWatchLogMonitor = options.traceLogs ? new CloudWatchLogEventMonitor({ ioHelper }) : undefined;
641641
const deployAndWatch = async () => {
642642
latch = 'deploying' as LatchState;
643-
cloudWatchLogMonitor?.deactivate();
643+
await cloudWatchLogMonitor?.deactivate();
644644

645645
await this.invokeDeployFromWatch(assembly, options, cloudWatchLogMonitor);
646646

@@ -654,7 +654,7 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
654654
await this.invokeDeployFromWatch(assembly, options, cloudWatchLogMonitor);
655655
}
656656
latch = 'open';
657-
cloudWatchLogMonitor?.activate();
657+
await cloudWatchLogMonitor?.activate();
658658
};
659659

660660
chokidar

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { CloudFormationStackArtifact, Environment } from '@aws-cdk/cx-api';
22
import type { StackResourceSummary } from '@aws-sdk/client-cloudformation';
3-
import { IoHelper } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private';
4-
import { debug } from '../../logging';
3+
import { IO, IoHelper } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private';
54
import { formatErrorMessage } from '../../util';
65
import type { SDK, SdkProvider } from '../aws-auth';
76
import { EnvironmentAccess } from '../environment';
@@ -47,7 +46,7 @@ export async function findCloudWatchLogGroups(
4746
try {
4847
sdk = (await new EnvironmentAccess(sdkProvider, DEFAULT_TOOLKIT_STACK_NAME, ioHelper).accessStackForLookup(stackArtifact)).sdk;
4948
} catch (e: any) {
50-
debug(`Failed to access SDK environment: ${formatErrorMessage(e)}`);
49+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`Failed to access SDK environment: ${formatErrorMessage(e)}`));
5150
sdk = (await sdkProvider.forEnvironment(resolvedEnv, Mode.ForReading)).sdk;
5251
}
5352

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

+78-52
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,12 @@
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 type { CloudWatchLogEvent } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io';
6+
import { IO, IoHelper } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private';
57
import { flatten } from '../../util';
68
import type { SDK } from '../aws-auth';
79

8-
/**
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
14-
*/
15-
const SLEEP = 2_000;
16-
17-
/**
18-
* Represents a CloudWatch Log Event that will be
19-
* printed to the terminal
20-
*/
21-
interface CloudWatchLogEvent {
22-
/**
23-
* The log event message
24-
*/
25-
readonly message: string;
26-
27-
/**
28-
* The name of the log group
29-
*/
30-
readonly logGroupName: string;
31-
32-
/**
33-
* The time at which the event occurred
34-
*/
35-
readonly timestamp: Date;
36-
}
37-
3810
/**
3911
* Configuration tracking information on the log groups that are
4012
* being monitored
@@ -54,6 +26,20 @@ interface LogGroupsAccessSettings {
5426
readonly logGroupsStartTimes: { [logGroupName: string]: number };
5527
}
5628

29+
export interface CloudWatchLogEventMonitorProps {
30+
/**
31+
* The IoHost used for messaging
32+
*/
33+
readonly ioHelper: IoHelper;
34+
35+
/**
36+
* The time from which we start reading log messages
37+
*
38+
* @default - now
39+
*/
40+
readonly startTime?: Date;
41+
}
42+
5743
export class CloudWatchLogEventMonitor {
5844
/**
5945
* Determines which events not to display
@@ -65,18 +51,36 @@ export class CloudWatchLogEventMonitor {
6551
*/
6652
private readonly envsLogGroupsAccessSettings = new Map<string, LogGroupsAccessSettings>();
6753

68-
private active = false;
54+
/**
55+
* After reading events from all CloudWatch log groups
56+
* how long should we wait to read more events.
57+
*
58+
* If there is some error with reading events (i.e. Throttle)
59+
* then this is also how long we wait until we try again
60+
*/
61+
private readonly pollingInterval: number = 2_000;
62+
63+
public monitorId?: string;
64+
private readonly ioHelper: IoHelper;
6965

70-
constructor(startTime?: Date) {
71-
this.startTime = startTime?.getTime() ?? Date.now();
66+
constructor(props: CloudWatchLogEventMonitorProps) {
67+
this.startTime = props.startTime?.getTime() ?? Date.now();
68+
this.ioHelper = props.ioHelper;
7269
}
7370

7471
/**
7572
* resume reading/printing events
7673
*/
77-
public activate(): void {
78-
this.active = true;
79-
this.scheduleNextTick(0);
74+
public async activate(): Promise<void> {
75+
this.monitorId = uuid.v4();
76+
77+
await this.ioHelper.notify(IO.CDK_TOOLKIT_I5032.msg('Start monitoring log groups', {
78+
monitor: this.monitorId,
79+
logGroupNames: this.logGroupNames(),
80+
}));
81+
82+
await this.tick();
83+
this.scheduleNextTick();
8084
}
8185

8286
/**
@@ -88,9 +92,16 @@ export class CloudWatchLogEventMonitor {
8892
* Also resets the start time to be when the new deployment was triggered
8993
* and clears the list of tracked log groups
9094
*/
91-
public deactivate(): void {
92-
this.active = false;
95+
public async deactivate(): Promise<void> {
96+
const oldMonitorId = this.monitorId!;
97+
this.monitorId = undefined;
9398
this.startTime = Date.now();
99+
100+
await this.ioHelper.notify(IO.CDK_TOOLKIT_I5034.msg('Stopped monitoring log groups', {
101+
monitor: oldMonitorId,
102+
logGroupNames: this.logGroupNames(),
103+
}));
104+
94105
this.envsLogGroupsAccessSettings.clear();
95106
}
96107

@@ -119,27 +130,41 @@ export class CloudWatchLogEventMonitor {
119130
});
120131
}
121132

122-
private scheduleNextTick(sleep: number): void {
123-
setTimeout(() => void this.tick(), sleep);
133+
private logGroupNames(): string[] {
134+
return Array.from(this.envsLogGroupsAccessSettings.values()).flatMap((settings) => Object.keys(settings.logGroupsStartTimes));
135+
}
136+
137+
private scheduleNextTick(): void {
138+
if (!this.monitorId) {
139+
return;
140+
}
141+
142+
setTimeout(() => void this.tick(), this.pollingInterval);
124143
}
125144

126145
private async tick(): Promise<void> {
127146
// excluding from codecoverage because this
128147
// doesn't always run (depends on timing)
129-
/* istanbul ignore next */
130-
if (!this.active) {
148+
/* c8 ignore next */
149+
if (!this.monitorId) {
131150
return;
132151
}
152+
133153
try {
134154
const events = flatten(await this.readNewEvents());
135-
events.forEach((event) => {
136-
this.print(event);
137-
});
138-
} catch (e) {
139-
error('Error occurred while monitoring logs: %s', e);
155+
for (const event of events) {
156+
await this.print(event);
157+
}
158+
159+
// We might have been stop()ped while the network call was in progress.
160+
if (!this.monitorId) {
161+
return;
162+
}
163+
} catch (e: any) {
164+
await this.ioHelper.notify(IO.CDK_TOOLKIT_E5035.msg('Error occurred while monitoring logs: %s', { error: e }));
140165
}
141166

142-
this.scheduleNextTick(SLEEP);
167+
this.scheduleNextTick();
143168
}
144169

145170
/**
@@ -161,15 +186,16 @@ export class CloudWatchLogEventMonitor {
161186
/**
162187
* Print out a cloudwatch event
163188
*/
164-
private print(event: CloudWatchLogEvent): void {
165-
info(
189+
private async print(event: CloudWatchLogEvent): Promise<void> {
190+
await this.ioHelper.notify(IO.CDK_TOOLKIT_I5033.msg(
166191
util.format(
167192
'[%s] %s %s',
168193
chalk.blue(event.logGroupName),
169194
chalk.yellow(event.timestamp.toLocaleTimeString()),
170195
event.message.trim(),
171196
),
172-
);
197+
event,
198+
));
173199
}
174200

175201
/**

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,7 @@ export class CdkToolkit {
649649

650650
public async watch(options: WatchOptions) {
651651
const rootDir = path.dirname(path.resolve(PROJECT_CONFIG));
652+
const ioHelper = asIoHelper(this.ioHost, 'watch');
652653
debug("root directory used for 'watch' is: %s", rootDir);
653654

654655
const watchSettings: { include?: string | string[]; exclude: string | string[] } | undefined =
@@ -697,10 +698,12 @@ export class CdkToolkit {
697698
// -------------- -------- 'cdk deploy' done -------------- 'cdk deploy' done --------------
698699
let latch: 'pre-ready' | 'open' | 'deploying' | 'queued' = 'pre-ready';
699700

700-
const cloudWatchLogMonitor = options.traceLogs ? new CloudWatchLogEventMonitor() : undefined;
701+
const cloudWatchLogMonitor = options.traceLogs ? new CloudWatchLogEventMonitor({
702+
ioHelper,
703+
}) : undefined;
701704
const deployAndWatch = async () => {
702705
latch = 'deploying';
703-
cloudWatchLogMonitor?.deactivate();
706+
await cloudWatchLogMonitor?.deactivate();
704707

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

@@ -714,7 +717,7 @@ export class CdkToolkit {
714717
await this.invokeDeployFromWatch(options, cloudWatchLogMonitor);
715718
}
716719
latch = 'open';
717-
cloudWatchLogMonitor?.activate();
720+
await cloudWatchLogMonitor?.activate();
718721
};
719722

720723
chokidar

0 commit comments

Comments
 (0)