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:
|
||||
|
||||
* `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`,
|
||||
|
@ -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) {
|
||||
|
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
|
||||
// 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.');
|
||||
|
@ -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'));
|
||||
});
|
||||
}
|
||||
|
@ -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())');
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user