11import * as util from 'util' ;
22import * as cxapi from '@aws-cdk/cx-api' ;
33import * 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' ;
57import { flatten } from '../../util' ;
68import 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+
5786export 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 /**
0 commit comments