Skip to content

Commit 1940114

Browse files
committed
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: #27052 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 693401d commit 1940114

File tree

6 files changed

+133
-13
lines changed

6 files changed

+133
-13
lines changed

doc/api/util.md

+10-9
Original file line numberDiff line numberDiff line change
@@ -638,15 +638,16 @@ via the `util.inspect.styles` and `util.inspect.colors` properties.
638638

639639
The default styles and associated colors are:
640640

641-
* `number` - `yellow`
642-
* `boolean` - `yellow`
643-
* `string` - `green`
644-
* `date` - `magenta`
645-
* `regexp` - `red`
646-
* `null` - `bold`
647-
* `undefined` - `grey`
648-
* `special` - `cyan` (only applied to functions at this time)
649-
* `name` - (no styling)
641+
* `number` - `yellow`
642+
* `boolean` - `yellow`
643+
* `string` - `green`
644+
* `date` - `magenta`
645+
* `module` - `underline`
646+
* `regexp` - `red`
647+
* `null` - `bold`
648+
* `undefined` - `grey`
649+
* `special` - `cyan` (only applied to functions at this time)
650+
* `name` - (no styling)
650651

651652
The predefined color codes are: `white`, `grey`, `black`, `blue`, `cyan`,
652653
`green`, `magenta`, `red` and `yellow`. There are also `bold`, `italic`,

lib/internal/util/inspect.js

+35-2
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ const {
8686

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

89+
const { NativeModule } = require('internal/bootstrap/loaders');
90+
8991
let hexSlice;
9092

9193
const inspectDefaultOptions = Object.seal({
@@ -115,6 +117,9 @@ const strEscapeSequencesReplacerSingle = /[\x00-\x1f\x5c]/g;
115117
const keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/;
116118
const numberRegExp = /^(0|[1-9][0-9]*)$/;
117119

120+
const coreModuleRegExp = /^ at (?:[^/\\(]+ \(|)((?<![/\\]).+)\.js:\d+:\d+\)?$/;
121+
const nodeModulesRegExp = /[/\\]node_modules[/\\](.+?)(?=[/\\])/g;
122+
118123
const readableRegExps = {};
119124

120125
const kMinLineLength = 16;
@@ -253,7 +258,8 @@ inspect.styles = Object.assign(Object.create(null), {
253258
symbol: 'green',
254259
date: 'magenta',
255260
// "name": intentionally not styling
256-
regexp: 'red'
261+
regexp: 'red',
262+
module: 'underline'
257263
});
258264

259265
function addQuotes(str, quotes) {
@@ -838,10 +844,37 @@ function formatError(err, constructor, tag, ctx) {
838844
}
839845
}
840846
}
847+
// Ignore the error message if it's contained in the stack.
848+
let pos = err.message && stack.indexOf(err.message) || -1;
849+
if (pos !== -1)
850+
pos += err.message.length;
841851
// Wrap the error in brackets in case it has no stack trace.
842-
const stackStart = stack.indexOf('\n at');
852+
const stackStart = stack.indexOf('\n at', pos);
843853
if (stackStart === -1) {
844854
stack = `[${stack}]`;
855+
} else if (ctx.colors) {
856+
// Highlight userland code and node modules.
857+
let newStack = stack.slice(0, stackStart);
858+
const lines = stack.slice(stackStart + 1).split('\n');
859+
for (const line of lines) {
860+
const core = line.match(coreModuleRegExp);
861+
if (core !== null && NativeModule.exists(core[1])) {
862+
newStack += `\n${ctx.stylize(line, 'undefined')}`;
863+
} else {
864+
// This adds underscores to all node_modules to quickly identify them.
865+
let nodeModule;
866+
newStack += '\n';
867+
let pos = 0;
868+
while (nodeModule = nodeModulesRegExp.exec(line)) {
869+
// '/node_modules/'.length === 14
870+
newStack += line.slice(pos, nodeModule.index + 14);
871+
newStack += ctx.stylize(nodeModule[1], 'module');
872+
pos = nodeModule.index + nodeModule[0].length;
873+
}
874+
newStack += pos === 0 ? line : line.slice(pos);
875+
}
876+
}
877+
stack = newStack;
845878
}
846879
// The message and the stack have to be indented as well!
847880
if (ctx.indentationLvl !== 0) {

test/fixtures/node_modules/node_modules/bar.js

-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/parallel/test-util-inspect.js

+40
Original file line numberDiff line numberDiff line change
@@ -2311,3 +2311,43 @@ assert.strictEqual(
23112311

23122312
assert.strictEqual(out, expected);
23132313
}
2314+
2315+
{
2316+
// Use a fake stack to verify the expected colored outcome.
2317+
const stack = [
2318+
'TypedError: Wonderful message!',
2319+
' at A.<anonymous> (/test/node_modules/foo/node_modules/bar/baz.js:2:7)',
2320+
' at Module._compile (internal/modules/cjs/loader.js:827:30)',
2321+
' at Fancy (vm.js:697:32)',
2322+
// This file is not an actual Node.js core file.
2323+
' at tryModuleLoad (internal/modules/cjs/foo.js:629:12)',
2324+
' at Function.Module._load (internal/modules/cjs/loader.js:621:3)',
2325+
// This file is not an actual Node.js core file.
2326+
' at Module.require [as weird/name] (internal/aaaaaa/loader.js:735:19)',
2327+
' at require (internal/modules/cjs/helpers.js:14:16)',
2328+
' at /test/test-util-inspect.js:2239:9',
2329+
' at getActual (assert.js:592:5)'
2330+
];
2331+
const isNodeCoreFile = [
2332+
false, false, true, true, false, true, false, true, false, true
2333+
];
2334+
const err = new TypeError('Wonderful message!');
2335+
err.stack = stack.join('\n');
2336+
util.inspect(err, { colors: true }).split('\n').forEach((line, i) => {
2337+
let actual = stack[i].replace(/node_modules\/([a-z]+)/g, (a, m) => {
2338+
return `node_modules/\u001b[4m${m}\u001b[24m`;
2339+
});
2340+
if (isNodeCoreFile[i]) {
2341+
actual = `\u001b[90m${actual}\u001b[39m`;
2342+
}
2343+
assert.strictEqual(actual, line);
2344+
});
2345+
}
2346+
2347+
{
2348+
// Cross platform checks.
2349+
const err = new Error('foo');
2350+
util.inspect(err, { colors: true }).split('\n').forEach((line, i) => {
2351+
assert(i < 2 || line.startsWith('\u001b[90m'));
2352+
});
2353+
}

test/pseudo-tty/console_colors.js

+13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
'use strict';
22
require('../common');
3+
const vm = require('vm');
34
// Make this test OS-independent by overriding stdio getColorDepth().
45
process.stdout.getColorDepth = () => 8;
56
process.stderr.getColorDepth = () => 8;
67

78
console.log({ foo: 'bar' });
89
console.log('%s q', 'string');
910
console.log('%o with object format param', { foo: 'bar' });
11+
12+
console.log(
13+
new Error('test\n at abc (../fixtures/node_modules/bar.js:4:4)\nfoobar')
14+
);
15+
16+
try {
17+
require('../fixtures/node_modules/node_modules/bar.js');
18+
} catch (err) {
19+
console.log(err);
20+
}
21+
22+
vm.runInThisContext('console.log(new Error())');

test/pseudo-tty/console_colors.out

+35
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,38 @@
11
{ foo: *[32m'bar'*[39m }
22
string q
33
{ foo: *[32m'bar'*[39m } with object format param
4+
5+
Error: test
6+
at abc (../fixtures/node_modules/bar.js:4:4)
7+
foobar
8+
at * (*console_colors.js:*:*)
9+
*[90m at * (internal*:*:*)*[39m
10+
*[90m at *[39m
11+
*[90m at *[39m
12+
*[90m at *[39m
13+
*[90m at *[39m
14+
*[90m at *[39m
15+
16+
Error: Should not ever get here.
17+
at * (*node_modules*[4m*node_modules*[24m*bar.js:*:*)
18+
*[90m at *[39m
19+
*[90m at *[39m
20+
*[90m at *[39m
21+
*[90m at *[39m
22+
*[90m at *[39m
23+
*[90m at *[39m
24+
at * (*console_colors.js:*:*)
25+
*[90m at *[39m
26+
*[90m at *[39m
27+
28+
Error
29+
at evalmachine.<anonymous>:*:*
30+
*[90m at Script.runInThisContext (vm.js:*:*)*[39m
31+
*[90m at Object.runInThisContext (vm.js:*:*)*[39m
32+
at * (*console_colors.js:*:*)
33+
*[90m at *[39m
34+
*[90m at *[39m
35+
*[90m at *[39m
36+
*[90m at *[39m
37+
*[90m at *[39m
38+
*[90m at *[39m

0 commit comments

Comments
 (0)