Skip to content

Commit

Permalink
Colored console for node environment
Browse files Browse the repository at this point in the history
Status report in logger
ProgressPlugins uses logger
  • Loading branch information
sokra committed Aug 1, 2019
1 parent ba20513 commit 8f0a443
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 106 deletions.
83 changes: 21 additions & 62 deletions lib/ProgressPlugin.js
Expand Up @@ -10,84 +10,38 @@ const schema = require("../schemas/plugins/ProgressPlugin.json");
/** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginArgument} ProgressPluginArgument */
/** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginOptions} ProgressPluginOptions */

const createDefaultHandler = profile => {
let lineCaretPosition = 0;
let lastMessage = "";
const createDefaultHandler = (profile, logger) => {
let lastState;
let lastStateTime;

const defaultHandler = (percentage, msg, ...args) => {
let state = msg;
const details = args.filter(v => v.length);
const maxLineLength = process.stderr.columns || Infinity;
if (percentage < 1) {
percentage = Math.floor(percentage * 100);
msg = `${percentage}% ${msg}`;
if (percentage < 100) {
msg = ` ${msg}`;
}
if (percentage < 10) {
msg = ` ${msg}`;
}

if (details.length) {
const maxTotalDetailsLength = maxLineLength - msg.length;
const totalDetailsLength = details.reduce(
(a, b) => a + b.length,
details.length // account for added space before each detail text
);
const maxDetailLength =
totalDetailsLength < maxTotalDetailsLength
? Infinity
: Math.floor(maxTotalDetailsLength / details.length);

for (let detail of details) {
if (!detail) continue;
if (detail.length + 1 > maxDetailLength) {
const truncatePrefix = "...";
detail = `${truncatePrefix}${detail.substr(
-(maxDetailLength - truncatePrefix.length - 1)
)}`;
}
msg += ` ${detail}`;
}
}
}
logger.status(`${Math.floor(percentage * 100)}%`, msg, ...args);
if (profile) {
let state = msg;
state = state.replace(/^\d+\/\d+\s+/, "");
if (percentage === 0) {
lastState = null;
lastStateTime = Date.now();
} else if (state !== lastState || percentage === 1) {
const now = Date.now();
if (lastState) {
const stateMsg = `${now - lastStateTime}ms ${lastState}`;
goToLineStart(stateMsg);
process.stderr.write(stateMsg + "\n");
lineCaretPosition = 0;
const diff = now - lastStateTime;
const stateMsg = `${diff}ms ${lastState}`;
if (diff > 1000) {
logger.warn(stateMsg);
} else if (diff > 10) {
logger.info(stateMsg);
} else if (diff > 0) {
logger.log(stateMsg);
} else {
logger.debug(stateMsg);
}
}
lastState = state;
lastStateTime = now;
}
}
if (lastMessage !== msg) {
goToLineStart(msg);
msg = msg.substring(0, maxLineLength);
process.stderr.write(msg);
lastMessage = msg;
}
};

const goToLineStart = nextMessage => {
let str = "";
for (; lineCaretPosition > nextMessage.length; lineCaretPosition--) {
str += "\b \b";
}
for (var i = 0; i < lineCaretPosition; i++) {
str += "\b";
}
lineCaretPosition = nextMessage.length;
if (str) process.stderr.write(str);
if (percentage === 1) logger.status();
};

return defaultHandler;
Expand Down Expand Up @@ -118,7 +72,12 @@ class ProgressPlugin {

apply(compiler) {
const { modulesCount } = this;
const handler = this.handler || createDefaultHandler(this.profile);
const handler =
this.handler ||
createDefaultHandler(
this.profile,
compiler.getInfrastructureLogger("webpack.Progress")
);
const showEntries = this.showEntries;
const showModules = this.showModules;
const showActiveModules = this.showActiveModules;
Expand Down
10 changes: 8 additions & 2 deletions lib/Stats.js
Expand Up @@ -755,6 +755,7 @@ class Stats {
LogType.profile,
LogType.profileEnd,
LogType.time,
LogType.status,
LogType.clear
]);
collapsedGroups = true;
Expand Down Expand Up @@ -1442,7 +1443,7 @@ class Stats {
let indent = "";
for (const entry of logData.entries) {
let color = colors.normal;
let prefix = "";
let prefix = " ";
switch (entry.type) {
case LogType.clear:
colors.normal(`${indent}-------`);
Expand All @@ -1467,6 +1468,10 @@ class Stats {
case LogType.debug:
color = colors.normal;
break;
case LogType.status:
color = colors.magenta;
prefix = "<s> ";
break;
case LogType.profile:
color = colors.magenta;
prefix = "<p> ";
Expand All @@ -1481,13 +1486,14 @@ class Stats {
break;
case LogType.group:
color = colors.cyan;
prefix = "<-> ";
break;
case LogType.groupCollapsed:
color = colors.cyan;
prefix = "<+> ";
break;
case LogType.groupEnd:
if (indent.length > 2)
if (indent.length >= 2)
indent = indent.slice(0, indent.length - 2);
continue;
}
Expand Down
7 changes: 6 additions & 1 deletion lib/logging/Logger.js
Expand Up @@ -26,7 +26,8 @@ const LogType = Object.freeze({

time: "time", // name, time as [seconds, nanoseconds]

clear: "clear" // no arguments
clear: "clear", // no arguments
status: "status" // message, arguments
});

exports.LogType = LogType;
Expand Down Expand Up @@ -78,6 +79,10 @@ class WebpackLogger {
this[LOG_SYMBOL](LogType.clear);
}

status(...args) {
this[LOG_SYMBOL](LogType.status, args);
}

group(...args) {
this[LOG_SYMBOL](LogType.group, args);
}
Expand Down
71 changes: 46 additions & 25 deletions lib/logging/createConsoleLogger.js
Expand Up @@ -15,8 +15,9 @@ const { LogType } = require("./Logger");

/**
* @typedef {Object} LoggerOptions
* @property {false|true|"none"|"error"|"warn"|"info"|"log"|"verbose"} options.level loglevel
* @property {FilterTypes|boolean} options.debug filter for debug logging
* @property {false|true|"none"|"error"|"warn"|"info"|"log"|"verbose"} level loglevel
* @property {FilterTypes|boolean} debug filter for debug logging
* @property {Console & { status?: Function, logTime?: Function }} console the console to log to
*/

/**
Expand Down Expand Up @@ -62,7 +63,7 @@ const LogLevel = {
* @param {LoggerOptions} options options object
* @returns {function(string, LogTypeEnum, any[]): void} logging function
*/
module.exports = ({ level = "info", debug = false }) => {
module.exports = ({ level = "info", debug = false, console }) => {
const debugFilters =
typeof debug === "boolean"
? [() => debug]
Expand All @@ -79,12 +80,12 @@ module.exports = ({ level = "info", debug = false }) => {
* @returns {void}
*/
const logger = (name, type, args) => {
const labeledArgs = (prefix = "") => {
const labeledArgs = () => {
if (Array.isArray(args)) {
if (args.length > 0 && typeof args[0] === "string") {
return [`${prefix}[${name}] ${args[0]}`, ...args.slice(1)];
return [`[${name}] ${args[0]}`, ...args.slice(1)];
} else {
return [`${prefix}[${name}]`, ...args];
return [`[${name}]`, ...args];
}
} else {
return [];
Expand All @@ -108,20 +109,33 @@ module.exports = ({ level = "info", debug = false }) => {
break;
case LogType.info:
if (!debug && loglevel > LogLevel.info) return;
console.info(...labeledArgs("<i> "));
console.info(...labeledArgs());
break;
case LogType.warn:
if (!debug && loglevel > LogLevel.warn) return;
console.warn(...labeledArgs("<w> "));
console.warn(...labeledArgs());
break;
case LogType.error:
if (!debug && loglevel > LogLevel.error) return;
console.error(...labeledArgs("<e> "));
console.error(...labeledArgs());
break;
case LogType.trace:
if (!debug) return;
console.trace();
break;
case LogType.groupCollapsed:
if (!debug && loglevel > LogLevel.log) return;
if (!debug && loglevel > LogLevel.verbose) {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
if (typeof console.groupCollapsed === "function") {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
console.groupCollapsed(...labeledArgs());
} else {
console.log(...labeledArgs());
}
break;
}
// falls through
case LogType.group:
if (!debug && loglevel > LogLevel.log) return;
// eslint-disable-next-line node/no-unsupported-features/node-builtins
Expand All @@ -132,32 +146,25 @@ module.exports = ({ level = "info", debug = false }) => {
console.log(...labeledArgs());
}
break;
case LogType.groupCollapsed:
if (!debug && loglevel > LogLevel.log) return;
// eslint-disable-next-line node/no-unsupported-features/node-builtins
if (typeof console.groupCollapsed === "function") {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
console.groupCollapsed(...labeledArgs());
} else {
console.log(...labeledArgs("<g> "));
}
break;
case LogType.groupEnd:
if (!debug && loglevel > LogLevel.log) return;
// eslint-disable-next-line node/no-unsupported-features/node-builtins
if (typeof console.groupEnd === "function") {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
console.groupEnd();
} else {
console.log(...labeledArgs("</g> "));
}
break;
case LogType.time:
case LogType.time: {
if (!debug && loglevel > LogLevel.log) return;
console.log(
`[${name}] ${args[0]}: ${args[1] * 1000 + args[2] / 1000000}ms`
);
const ms = args[1] * 1000 + args[2] / 1000000;
const msg = `[${name}] ${args[0]}: ${ms}ms`;
if (typeof console.logTime === "function") {
console.logTime(msg);
} else {
console.log(msg);
}
break;
}
case LogType.profile:
// eslint-disable-next-line node/no-unsupported-features/node-builtins
if (typeof console.profile === "function") {
Expand All @@ -180,6 +187,20 @@ module.exports = ({ level = "info", debug = false }) => {
console.clear();
}
break;
case LogType.status:
if (!debug && loglevel > LogLevel.info) return;
if (typeof console.status === "function") {
if (args.length === 0) {
console.status();
} else {
console.status(...labeledArgs());
}
} else {
if (args.length !== 0) {
console.info(...labeledArgs());
}
}
break;
default:
throw new Error(`Unexpected LogType ${type}`);
}
Expand Down
3 changes: 2 additions & 1 deletion lib/logging/runtime.js
Expand Up @@ -5,7 +5,8 @@ const createConsoleLogger = require("./createConsoleLogger");
/** @type {createConsoleLogger.LoggerOptions} */
let currentDefaultLoggerOptions = {
level: "info",
debug: false
debug: false,
console
};
let currentDefaultLogger = createConsoleLogger(currentDefaultLoggerOptions);

Expand Down

0 comments on commit 8f0a443

Please sign in to comment.