util: highlight stack frames

Using `util.inspect` on errors is going to highlight userland and
node_module stack frames from now on. This is done by marking Node.js
core frames grey and frames that contain `node_modules` in their path
yellow.

That way it's easy to grasp what frames belong to what code.

PR-URL: https://github.com/nodejs/node/pull/27052
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Ruben Bridgewater 2019-04-02 07:56:14 +02:00
parent 693401d0dd
commit 1940114ac3
No known key found for this signature in database
GPG Key ID: F07496B3EB3C1762
6 changed files with 133 additions and 13 deletions

View File

@ -638,15 +638,16 @@ via the `util.inspect.styles` and `util.inspect.colors` properties.
The default styles and associated colors are:
* `number` - `yellow`
* `boolean` - `yellow`
* `string` - `green`
* `date` - `magenta`
* `regexp` - `red`
* `null` - `bold`
* `undefined` - `grey`
* `special` - `cyan` (only applied to functions at this time)
* `name` - (no styling)
* `number` - `yellow`
* `boolean` - `yellow`
* `string` - `green`
* `date` - `magenta`
* `module` - `underline`
* `regexp` - `red`
* `null` - `bold`
* `undefined` - `grey`
* `special` - `cyan` (only applied to functions at this time)
* `name` - (no styling)
The predefined color codes are: `white`, `grey`, `black`, `blue`, `cyan`,
`green`, `magenta`, `red` and `yellow`. There are also `bold`, `italic`,

View File

@ -86,6 +86,8 @@ const {
const assert = require('internal/assert');
const { NativeModule } = require('internal/bootstrap/loaders');
let hexSlice;
const inspectDefaultOptions = Object.seal({
@ -115,6 +117,9 @@ const strEscapeSequencesReplacerSingle = /[\x00-\x1f\x5c]/g;
const keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/;
const numberRegExp = /^(0|[1-9][0-9]*)$/;
const coreModuleRegExp = /^ at (?:[^/\\(]+ \(|)((?<![/\\]).+)\.js:\d+:\d+\)?$/;
const nodeModulesRegExp = /[/\\]node_modules[/\\](.+?)(?=[/\\])/g;
const readableRegExps = {};
const kMinLineLength = 16;
@ -253,7 +258,8 @@ inspect.styles = Object.assign(Object.create(null), {
symbol: 'green',
date: 'magenta',
// "name": intentionally not styling
regexp: 'red'
regexp: 'red',
module: 'underline'
});
function addQuotes(str, quotes) {
@ -838,10 +844,37 @@ function formatError(err, constructor, tag, ctx) {
}
}
}
// Ignore the error message if it's contained in the stack.
let pos = err.message && stack.indexOf(err.message) || -1;
if (pos !== -1)
pos += err.message.length;
// Wrap the error in brackets in case it has no stack trace.
const stackStart = stack.indexOf('\n at');
const stackStart = stack.indexOf('\n at', pos);
if (stackStart === -1) {
stack = `[${stack}]`;
} else if (ctx.colors) {
// Highlight userland code and node modules.
let newStack = stack.slice(0, stackStart);
const lines = stack.slice(stackStart + 1).split('\n');
for (const line of lines) {
const core = line.match(coreModuleRegExp);
if (core !== null && NativeModule.exists(core[1])) {
newStack += `\n${ctx.stylize(line, 'undefined')}`;
} else {
// This adds underscores to all node_modules to quickly identify them.
let nodeModule;
newStack += '\n';
let pos = 0;
while (nodeModule = nodeModulesRegExp.exec(line)) {
// '/node_modules/'.length === 14
newStack += line.slice(pos, nodeModule.index + 14);
newStack += ctx.stylize(nodeModule[1], 'module');
pos = nodeModule.index + nodeModule[0].length;
}
newStack += pos === 0 ? line : line.slice(pos);
}
}
stack = newStack;
}
// The message and the stack have to be indented as well!
if (ctx.indentationLvl !== 0) {

View File

@ -19,6 +19,4 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
console.error(__filename);
console.error(module.paths.join('\n') + '\n');
throw new Error('Should not ever get here.');

View File

@ -2311,3 +2311,43 @@ assert.strictEqual(
assert.strictEqual(out, expected);
}
{
// Use a fake stack to verify the expected colored outcome.
const stack = [
'TypedError: Wonderful message!',
' at A.<anonymous> (/test/node_modules/foo/node_modules/bar/baz.js:2:7)',
' at Module._compile (internal/modules/cjs/loader.js:827:30)',
' at Fancy (vm.js:697:32)',
// This file is not an actual Node.js core file.
' at tryModuleLoad (internal/modules/cjs/foo.js:629:12)',
' at Function.Module._load (internal/modules/cjs/loader.js:621:3)',
// This file is not an actual Node.js core file.
' at Module.require [as weird/name] (internal/aaaaaa/loader.js:735:19)',
' at require (internal/modules/cjs/helpers.js:14:16)',
' at /test/test-util-inspect.js:2239:9',
' at getActual (assert.js:592:5)'
];
const isNodeCoreFile = [
false, false, true, true, false, true, false, true, false, true
];
const err = new TypeError('Wonderful message!');
err.stack = stack.join('\n');
util.inspect(err, { colors: true }).split('\n').forEach((line, i) => {
let actual = stack[i].replace(/node_modules\/([a-z]+)/g, (a, m) => {
return `node_modules/\u001b[4m${m}\u001b[24m`;
});
if (isNodeCoreFile[i]) {
actual = `\u001b[90m${actual}\u001b[39m`;
}
assert.strictEqual(actual, line);
});
}
{
// Cross platform checks.
const err = new Error('foo');
util.inspect(err, { colors: true }).split('\n').forEach((line, i) => {
assert(i < 2 || line.startsWith('\u001b[90m'));
});
}

View File

@ -1,5 +1,6 @@
'use strict';
require('../common');
const vm = require('vm');
// Make this test OS-independent by overriding stdio getColorDepth().
process.stdout.getColorDepth = () => 8;
process.stderr.getColorDepth = () => 8;
@ -7,3 +8,15 @@ process.stderr.getColorDepth = () => 8;
console.log({ foo: 'bar' });
console.log('%s q', 'string');
console.log('%o with object format param', { foo: 'bar' });
console.log(
new Error('test\n at abc (../fixtures/node_modules/bar.js:4:4)\nfoobar')
);
try {
require('../fixtures/node_modules/node_modules/bar.js');
} catch (err) {
console.log(err);
}
vm.runInThisContext('console.log(new Error())');

View File

@ -1,3 +1,38 @@
{ foo: *[32m'bar'*[39m }
string q
{ foo: *[32m'bar'*[39m } with object format param
Error: test
at abc (../fixtures/node_modules/bar.js:4:4)
foobar
at * (*console_colors.js:*:*)
*[90m at * (internal*:*:*)*[39m
*[90m at *[39m
*[90m at *[39m
*[90m at *[39m
*[90m at *[39m
*[90m at *[39m
Error: Should not ever get here.
at * (*node_modules*[4m*node_modules*[24m*bar.js:*:*)
*[90m at *[39m
*[90m at *[39m
*[90m at *[39m
*[90m at *[39m
*[90m at *[39m
*[90m at *[39m
at * (*console_colors.js:*:*)
*[90m at *[39m
*[90m at *[39m
Error
at evalmachine.<anonymous>:*:*
*[90m at Script.runInThisContext (vm.js:*:*)*[39m
*[90m at Object.runInThisContext (vm.js:*:*)*[39m
at * (*console_colors.js:*:*)
*[90m at *[39m
*[90m at *[39m
*[90m at *[39m
*[90m at *[39m
*[90m at *[39m
*[90m at *[39m