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:
parent
693401d0dd
commit
1940114ac3
@ -638,15 +638,16 @@ via the `util.inspect.styles` and `util.inspect.colors` properties.
|
|||||||
|
|
||||||
The default styles and associated colors are:
|
The default styles and associated colors are:
|
||||||
|
|
||||||
* `number` - `yellow`
|
* `number` - `yellow`
|
||||||
* `boolean` - `yellow`
|
* `boolean` - `yellow`
|
||||||
* `string` - `green`
|
* `string` - `green`
|
||||||
* `date` - `magenta`
|
* `date` - `magenta`
|
||||||
* `regexp` - `red`
|
* `module` - `underline`
|
||||||
* `null` - `bold`
|
* `regexp` - `red`
|
||||||
* `undefined` - `grey`
|
* `null` - `bold`
|
||||||
* `special` - `cyan` (only applied to functions at this time)
|
* `undefined` - `grey`
|
||||||
* `name` - (no styling)
|
* `special` - `cyan` (only applied to functions at this time)
|
||||||
|
* `name` - (no styling)
|
||||||
|
|
||||||
The predefined color codes are: `white`, `grey`, `black`, `blue`, `cyan`,
|
The predefined color codes are: `white`, `grey`, `black`, `blue`, `cyan`,
|
||||||
`green`, `magenta`, `red` and `yellow`. There are also `bold`, `italic`,
|
`green`, `magenta`, `red` and `yellow`. There are also `bold`, `italic`,
|
||||||
|
@ -86,6 +86,8 @@ const {
|
|||||||
|
|
||||||
const assert = require('internal/assert');
|
const assert = require('internal/assert');
|
||||||
|
|
||||||
|
const { NativeModule } = require('internal/bootstrap/loaders');
|
||||||
|
|
||||||
let hexSlice;
|
let hexSlice;
|
||||||
|
|
||||||
const inspectDefaultOptions = Object.seal({
|
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 keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/;
|
||||||
const numberRegExp = /^(0|[1-9][0-9]*)$/;
|
const numberRegExp = /^(0|[1-9][0-9]*)$/;
|
||||||
|
|
||||||
|
const coreModuleRegExp = /^ at (?:[^/\\(]+ \(|)((?<![/\\]).+)\.js:\d+:\d+\)?$/;
|
||||||
|
const nodeModulesRegExp = /[/\\]node_modules[/\\](.+?)(?=[/\\])/g;
|
||||||
|
|
||||||
const readableRegExps = {};
|
const readableRegExps = {};
|
||||||
|
|
||||||
const kMinLineLength = 16;
|
const kMinLineLength = 16;
|
||||||
@ -253,7 +258,8 @@ inspect.styles = Object.assign(Object.create(null), {
|
|||||||
symbol: 'green',
|
symbol: 'green',
|
||||||
date: 'magenta',
|
date: 'magenta',
|
||||||
// "name": intentionally not styling
|
// "name": intentionally not styling
|
||||||
regexp: 'red'
|
regexp: 'red',
|
||||||
|
module: 'underline'
|
||||||
});
|
});
|
||||||
|
|
||||||
function addQuotes(str, quotes) {
|
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.
|
// 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) {
|
if (stackStart === -1) {
|
||||||
stack = `[${stack}]`;
|
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!
|
// The message and the stack have to be indented as well!
|
||||||
if (ctx.indentationLvl !== 0) {
|
if (ctx.indentationLvl !== 0) {
|
||||||
|
2
test/fixtures/node_modules/node_modules/bar.js
generated
vendored
2
test/fixtures/node_modules/node_modules/bar.js
generated
vendored
@ -19,6 +19,4 @@
|
|||||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// 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.');
|
throw new Error('Should not ever get here.');
|
||||||
|
@ -2311,3 +2311,43 @@ assert.strictEqual(
|
|||||||
|
|
||||||
assert.strictEqual(out, expected);
|
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'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
require('../common');
|
require('../common');
|
||||||
|
const vm = require('vm');
|
||||||
// Make this test OS-independent by overriding stdio getColorDepth().
|
// Make this test OS-independent by overriding stdio getColorDepth().
|
||||||
process.stdout.getColorDepth = () => 8;
|
process.stdout.getColorDepth = () => 8;
|
||||||
process.stderr.getColorDepth = () => 8;
|
process.stderr.getColorDepth = () => 8;
|
||||||
@ -7,3 +8,15 @@ process.stderr.getColorDepth = () => 8;
|
|||||||
console.log({ foo: 'bar' });
|
console.log({ foo: 'bar' });
|
||||||
console.log('%s q', 'string');
|
console.log('%s q', 'string');
|
||||||
console.log('%o with object format param', { foo: 'bar' });
|
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())');
|
||||||
|
@ -1,3 +1,38 @@
|
|||||||
{ foo: *[32m'bar'*[39m }
|
{ foo: *[32m'bar'*[39m }
|
||||||
string q
|
string q
|
||||||
{ foo: *[32m'bar'*[39m } with object format param
|
{ 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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user