forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconsole.ts
184 lines (157 loc) · 6.5 KB
/
console.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as chalk from 'chalk';
import {writeFileSync} from 'fs';
import {prompt} from 'inquirer';
import {join} from 'path';
import {Arguments} from 'yargs';
import {GitClient} from './git/git-client';
/** Reexport of chalk colors for convenient access. */
export const red = chalk.red;
export const green = chalk.green;
export const yellow = chalk.yellow;
export const bold = chalk.bold;
export const blue = chalk.blue;
/** Prompts the user with a confirmation question and a specified message. */
export async function promptConfirm(message: string, defaultValue = false): Promise<boolean> {
return (await prompt<{result: boolean}>({
type: 'confirm',
name: 'result',
message: message,
default: defaultValue,
}))
.result;
}
/** Prompts the user for one line of input. */
export async function promptInput(message: string): Promise<string> {
return (await prompt<{result: string}>({type: 'input', name: 'result', message})).result;
}
/**
* Supported levels for logging functions.
*
* Levels are mapped to numbers to represent a hierarchy of logging levels.
*/
export enum LOG_LEVELS {
SILENT = 0,
ERROR = 1,
WARN = 2,
LOG = 3,
INFO = 4,
DEBUG = 5,
}
/** Default log level for the tool. */
export const DEFAULT_LOG_LEVEL = LOG_LEVELS.INFO;
/** Write to the console for at INFO logging level */
export const info = buildLogLevelFunction(() => console.info, LOG_LEVELS.INFO);
/** Write to the console for at ERROR logging level */
export const error = buildLogLevelFunction(() => console.error, LOG_LEVELS.ERROR);
/** Write to the console for at DEBUG logging level */
export const debug = buildLogLevelFunction(() => console.debug, LOG_LEVELS.DEBUG);
/** Write to the console for at LOG logging level */
// tslint:disable-next-line: no-console
export const log = buildLogLevelFunction(() => console.log, LOG_LEVELS.LOG);
/** Write to the console for at WARN logging level */
export const warn = buildLogLevelFunction(() => console.warn, LOG_LEVELS.WARN);
/** Build an instance of a logging function for the provided level. */
function buildLogLevelFunction(loadCommand: () => Function, level: LOG_LEVELS) {
/** Write to stdout for the LOG_LEVEL. */
const loggingFunction = (...text: string[]) => {
runConsoleCommand(loadCommand, level, ...text);
};
/** Start a group at the LOG_LEVEL, optionally starting it as collapsed. */
loggingFunction.group = (text: string, collapsed = false) => {
const command = collapsed ? console.groupCollapsed : console.group;
runConsoleCommand(() => command, level, text);
};
/** End the group at the LOG_LEVEL. */
loggingFunction.groupEnd = () => {
runConsoleCommand(() => console.groupEnd, level);
};
return loggingFunction;
}
/**
* Run the console command provided, if the environments logging level greater than the
* provided logging level.
*
* The loadCommand takes in a function which is called to retrieve the console.* function
* to allow for jasmine spies to still work in testing. Without this method of retrieval
* the console.* function, the function is saved into the closure of the created logging
* function before jasmine can spy.
*/
function runConsoleCommand(loadCommand: () => Function, logLevel: LOG_LEVELS, ...text: string[]) {
if (getLogLevel() >= logLevel) {
loadCommand()(...text);
}
printToLogFile(logLevel, ...text);
}
/**
* Retrieve the log level from environment variables, if the value found
* based on the LOG_LEVEL environment variable is undefined, return the default
* logging level.
*/
function getLogLevel() {
const logLevelEnvValue: any = (process.env[`LOG_LEVEL`] || '').toUpperCase();
const logLevel = LOG_LEVELS[logLevelEnvValue];
if (logLevel === undefined) {
return DEFAULT_LOG_LEVEL;
}
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');
}
const git = GitClient.get();
/** 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 += `${headerLine}\n`;
LOGGED_TEXT += `Command ran in ${new Date().getTime() - now.getTime()}ms\n`;
LOGGED_TEXT += `Exit Code: ${code}\n`;
/** Path to the log file location. */
const logFilePath = join(git.baseDir, '.ng-dev.log');
// Strip ANSI escape codes from log outputs.
LOGGED_TEXT = LOGGED_TEXT.replace(/\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]/g, '');
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) {
const logFileName = `.ng-dev.err-${now.getTime()}.log`;
console.error(`Exit code: ${code}. Writing full log to ${logFileName}`);
writeFileSync(join(git.baseDir, logFileName), 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('');
}