From 3487b549fd1245f17e66722e36f9e3cc60798002 Mon Sep 17 00:00:00 2001 From: Joey Perrott Date: Wed, 26 Aug 2020 11:38:19 -0700 Subject: [PATCH] feat(dev-infra): write outputs of command runs to ng-dev log file (#38599) Creates infrastructure to write outputs of command runs to ng-dev log file. Additionally, on commands which fail with an exit code greater than 1, an error log file is created with the posix timestamp of the commands run time as an identifier. PR Close #38599 --- dev-infra/cli.ts | 2 ++ dev-infra/utils/BUILD.bazel | 4 +++ dev-infra/utils/console.ts | 58 +++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/dev-infra/cli.ts b/dev-infra/cli.ts index a220486865..5901dd9fe1 100644 --- a/dev-infra/cli.ts +++ b/dev-infra/cli.ts @@ -13,8 +13,10 @@ import {buildCommitMessageParser} from './commit-message/cli'; import {buildFormatParser} from './format/cli'; import {buildReleaseParser} from './release/cli'; import {buildPrParser} from './pr/cli'; +import {captureLogOutputForCommand} from './utils/console'; yargs.scriptName('ng-dev') + .middleware(captureLogOutputForCommand) .demandCommand() .recommendCommands() .command('commit-message ', '', buildCommitMessageParser) diff --git a/dev-infra/utils/BUILD.bazel b/dev-infra/utils/BUILD.bazel index 3881ac19b0..8bbddafe10 100644 --- a/dev-infra/utils/BUILD.bazel +++ b/dev-infra/utils/BUILD.bazel @@ -12,14 +12,18 @@ ts_library( "@npm//@octokit/graphql", "@npm//@octokit/rest", "@npm//@octokit/types", + "@npm//@types/fs-extra", "@npm//@types/inquirer", "@npm//@types/node", "@npm//@types/shelljs", + "@npm//@types/yargs", "@npm//chalk", + "@npm//fs-extra", "@npm//inquirer", "@npm//inquirer-autocomplete-prompt", "@npm//shelljs", "@npm//tslib", "@npm//typed-graphqlify", + "@npm//yargs", ], ) diff --git a/dev-infra/utils/console.ts b/dev-infra/utils/console.ts index 625cf9189a..d8676c48c4 100644 --- a/dev-infra/utils/console.ts +++ b/dev-infra/utils/console.ts @@ -7,9 +7,13 @@ */ import chalk from 'chalk'; +import {writeFileSync} from 'fs-extra'; import {createPromptModule, ListChoiceOptions, prompt} from 'inquirer'; import * as inquirerAutocomplete from 'inquirer-autocomplete-prompt'; +import {join} from 'path'; +import {Arguments} from 'yargs'; +import {getRepoBaseDir} from './config'; /** Reexport of chalk colors for convenient access. */ export const red: typeof chalk = chalk.red; @@ -140,6 +144,7 @@ function runConsoleCommand(loadCommand: () => Function, logLevel: LOG_LEVELS, .. if (getLogLevel() >= logLevel) { loadCommand()(...text); } + printToLogFile(logLevel, ...text); } /** @@ -155,3 +160,56 @@ function getLogLevel() { } return logLevel; } + +/** All text to write to the log file. */ +let LOGGED_TEXT = ''; +/** Whether file logging as been enabled. */ +let FILE_LOGGING_ENABLED = false; +/** + * The number of columns used in the prepended log level information on each line of the logging + * output file. + */ +const LOG_LEVEL_COLUMNS = 7; + +/** + * Enable writing the logged outputs to the log file on process exit, sets initial lines from the + * command execution, containing information about the timing and command parameters. + * + * This is expected to be called only once during a command run, and should be called by the + * middleware of yargs to enable the file logging before the rest of the command parsing and + * response is executed. + */ +export function captureLogOutputForCommand(argv: Arguments) { + if (FILE_LOGGING_ENABLED) { + throw Error('`captureLogOutputForCommand` cannot be called multiple times'); + } + /** The date time used for timestamping when the command was invoked. */ + const now = new Date(); + /** Header line to separate command runs in log files. */ + const headerLine = Array(100).fill('#').join(''); + LOGGED_TEXT += `${headerLine}\nCommand: ${argv.$0} ${argv._.join(' ')}\nRan at: ${now}\n`; + + // On process exit, write the logged output to the appropriate log files + process.on('exit', (code: number) => { + LOGGED_TEXT += `Command ran in ${new Date().getTime() - now.getTime()}ms`; + /** Path to the log file location. */ + const logFilePath = join(getRepoBaseDir(), '.ng-dev.log'); + + writeFileSync(logFilePath, LOGGED_TEXT); + + // For failure codes greater than 1, the new logged lines should be written to a specific log + // file for the command run failure. + if (code > 1) { + writeFileSync(join(getRepoBaseDir(), `.ng-dev.err-${now.getTime()}.log`), LOGGED_TEXT); + } + }); + + // Mark file logging as enabled to prevent the function from executing multiple times. + FILE_LOGGING_ENABLED = true; +} + +/** Write the provided text to the log file, prepending each line with the log level. */ +function printToLogFile(logLevel: LOG_LEVELS, ...text: string[]) { + const logLevelText = `${LOG_LEVELS[logLevel]}:`.padEnd(LOG_LEVEL_COLUMNS); + LOGGED_TEXT += text.join(' ').split('\n').map(l => `${logLevelText} ${l}\n`).join(''); +}