Skip to content

Commit

Permalink
util: highlight stack frames
Browse files Browse the repository at this point in the history
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: #27052
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
BridgeAR committed Apr 15, 2019
1 parent 693401d commit 1940114
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 13 deletions.
19 changes: 10 additions & 9 deletions doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
37 changes: 35 additions & 2 deletions lib/internal/util/inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ const {

const assert = require('internal/assert');

const { NativeModule } = require('internal/bootstrap/loaders');

let hexSlice;

const inspectDefaultOptions = Object.seal({
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 0 additions & 2 deletions test/fixtures/node_modules/node_modules/bar.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 40 additions & 0 deletions test/parallel/test-util-inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
});
}
13 changes: 13 additions & 0 deletions test/pseudo-tty/console_colors.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
'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;

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())');
35 changes: 35 additions & 0 deletions test/pseudo-tty/console_colors.out
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 1940114

Please sign in to comment.