packages/@aws-cdk/toolkit-lib/lib/private/activity-printer/history.ts (102 lines of code) (raw):

import * as util from 'util'; import * as chalk from 'chalk'; import type { ActivityPrinterProps } from './base'; import { ActivityPrinterBase } from './base'; import type { StackActivity } from '../../payloads'; import { padRight } from '../../util'; /** * Activity Printer which shows a full log of all CloudFormation events * * When there hasn't been activity for a while, it will print the resources * that are currently in progress, to show what's holding up the deployment. */ export class HistoryActivityPrinter extends ActivityPrinterBase { /** * Last time we printed something to the console. * * Used to measure timeout for progress reporting. */ private lastPrintTime = Date.now(); private lastPrinted?: StackActivity; /** * Number of ms of change absence before we tell the user about the resources that are currently in progress. */ private readonly inProgressDelay = 30_000; private readonly printable = new Array<StackActivity>(); constructor(props: ActivityPrinterProps) { super(props); } public activity(activity: StackActivity) { this.printable.push(activity); super.activity(activity); } public stop() { super.stop(); // Print failures at the end if (this.failures.length > 0) { this.stream.write('\nFailed resources:\n'); for (const failure of this.failures) { // Root stack failures are not interesting if (this.isActivityForTheStack(failure)) { continue; } this.printOne(failure, false); } } } protected print() { for (const activity of this.printable) { this.printOne(activity); this.lastPrinted = activity; } this.printable.splice(0, this.printable.length); this.printInProgress(this.lastPrinted?.progress.formatted); } private printOne(activity: StackActivity, progress?: boolean) { const event = activity.event; const color = colorFromStatusResult(event.ResourceStatus); let reasonColor = chalk.cyan; let stackTrace = ''; const metadata = activity.metadata; if (event.ResourceStatus && event.ResourceStatus.indexOf('FAILED') !== -1) { if (progress == undefined || progress) { event.ResourceStatusReason = event.ResourceStatusReason ? this.failureReason(activity) : ''; } if (metadata) { stackTrace = metadata.entry.trace ? `\n\t${metadata.entry.trace.join('\n\t\\_ ')}` : ''; } reasonColor = chalk.red; } const resourceName = metadata ? metadata.constructPath : event.LogicalResourceId || ''; const logicalId = resourceName !== event.LogicalResourceId ? `(${event.LogicalResourceId}) ` : ''; this.stream.write( util.format( '%s | %s%s | %s | %s | %s %s%s%s\n', event.StackName, progress !== false ? `${activity.progress.formatted} | ` : '', new Date(event.Timestamp!).toLocaleTimeString(), color(padRight(HistoryActivityPrinter.STATUS_WIDTH, (event.ResourceStatus || '').slice(0, HistoryActivityPrinter.STATUS_WIDTH))), // pad left and trim padRight(this.resourceTypeColumnWidth, event.ResourceType || ''), color(chalk.bold(resourceName)), logicalId, reasonColor(chalk.bold(event.ResourceStatusReason ? event.ResourceStatusReason : '')), reasonColor(stackTrace), ), ); this.lastPrintTime = Date.now(); } /** * If some resources are taking a while to create, notify the user about what's currently in progress */ private printInProgress(progress?: string) { if (!progress || Date.now() < this.lastPrintTime + this.inProgressDelay) { return; } if (Object.keys(this.resourcesInProgress).length > 0) { this.stream.write( util.format( '%s Currently in progress: %s\n', progress, chalk.bold(Object.keys(this.resourcesInProgress).join(', ')), ), ); } // We cheat a bit here. To prevent printInProgress() from repeatedly triggering, // we set the timestamp into the future. It will be reset whenever a regular print // occurs, after which we can be triggered again. this.lastPrintTime = +Infinity; } } function colorFromStatusResult(status?: string) { if (!status) { return chalk.reset; } if (status.indexOf('FAILED') !== -1) { return chalk.red; } if (status.indexOf('ROLLBACK') !== -1) { return chalk.yellow; } if (status.indexOf('COMPLETE') !== -1) { return chalk.green; } return chalk.reset; }