Skip to content

Commit a795424

Browse files
committed
util: always visualize cause property in errors during inspection
While inspecting errors, always visualize the cause. That property is non-enumerable by default while being useful in general for debugging. Duplicated stack frames are hidden. Signed-off-by: Ruben Bridgewater <[email protected]>
1 parent 265a47d commit a795424

File tree

3 files changed

+162
-13
lines changed

3 files changed

+162
-13
lines changed

lib/internal/util/inspect.js

+73-13
Original file line numberDiff line numberDiff line change
@@ -1162,25 +1162,58 @@ function getFunctionBase(value, constructor, tag) {
11621162
return base;
11631163
}
11641164

1165-
function formatError(err, constructor, tag, ctx, keys) {
1166-
const name = err.name != null ? String(err.name) : 'Error';
1167-
let len = name.length;
1168-
let stack = err.stack ? String(err.stack) : ErrorPrototypeToString(err);
1165+
function identicalSequenceRange(a, b) {
1166+
for (let i = 0; i < a.length - 3; i++) {
1167+
// Find the first entry of b that matches the current entry of a.
1168+
const pos = b.indexOf(a[i]);
1169+
if (pos !== -1) {
1170+
const rest = b.length - pos;
1171+
if (rest > 3) {
1172+
let len = 1;
1173+
const maxLen = MathMin(a.length - i, rest);
1174+
// Count the number of consecutive entries.
1175+
while (maxLen > len && a[i + len] === b[pos + len]) {
1176+
len++;
1177+
}
1178+
if (len > 3) {
1179+
return { len, offset: i };
1180+
}
1181+
}
1182+
}
1183+
}
11691184

1170-
// Do not "duplicate" error properties that are already included in the output
1171-
// otherwise.
1172-
if (!ctx.showHidden && keys.length !== 0) {
1173-
for (const name of ['name', 'message', 'stack']) {
1174-
const index = keys.indexOf(name);
1175-
// Only hide the property in case it's part of the original stack
1176-
if (index !== -1 && stack.includes(err[name])) {
1177-
keys.splice(index, 1);
1185+
return { len: 0, offset: 0 };
1186+
}
1187+
1188+
function getStackString(error) {
1189+
return error.stack ? String(error.stack) : ErrorPrototypeToString(error);
1190+
}
1191+
1192+
function getStackFrames(ctx, err, stack) {
1193+
const frames = stack.split('\n');
1194+
1195+
// Remove stack frames identical to frames in cause.
1196+
if (err.cause) {
1197+
const causeStack = getStackString(err.cause);
1198+
const causeStackStart = causeStack.indexOf('\n at');
1199+
if (causeStackStart !== -1) {
1200+
const causeFrames = causeStack.slice(causeStackStart + 1).split('\n');
1201+
const { len, offset } = identicalSequenceRange(frames, causeFrames);
1202+
if (len > 0) {
1203+
const skipped = len - 2;
1204+
const msg = ` ... ${skipped} lines matching cause stack trace ...`;
1205+
frames.splice(offset + 1, skipped, ctx.stylize(msg, 'undefined'));
11781206
}
11791207
}
11801208
}
1209+
return frames;
1210+
}
11811211

1212+
function improveStack(stack, constructor, name, tag) {
11821213
// A stack trace may contain arbitrary data. Only manipulate the output
11831214
// for "regular errors" (errors that "look normal") for now.
1215+
let len = name.length;
1216+
11841217
if (constructor === null ||
11851218
(name.endsWith('Error') &&
11861219
stack.startsWith(name) &&
@@ -1206,6 +1239,33 @@ function formatError(err, constructor, tag, ctx, keys) {
12061239
}
12071240
}
12081241
}
1242+
return stack;
1243+
}
1244+
1245+
function removeDuplicateErrorKeys(ctx, keys, err, stack) {
1246+
if (!ctx.showHidden && keys.length !== 0) {
1247+
for (const name of ['name', 'message', 'stack']) {
1248+
const index = keys.indexOf(name);
1249+
// Only hide the property in case it's part of the original stack
1250+
if (index !== -1 && stack.includes(err[name])) {
1251+
keys.splice(index, 1);
1252+
}
1253+
}
1254+
}
1255+
}
1256+
1257+
function formatError(err, constructor, tag, ctx, keys) {
1258+
const name = err.name != null ? String(err.name) : 'Error';
1259+
let stack = getStackString(err);
1260+
1261+
removeDuplicateErrorKeys(ctx, keys, err, stack);
1262+
1263+
if (err.cause && (keys.length === 0 || !keys.includes('cause'))) {
1264+
keys.push('cause');
1265+
}
1266+
1267+
stack = improveStack(stack, constructor, name, tag);
1268+
12091269
// Ignore the error message if it's contained in the stack.
12101270
let pos = (err.message && stack.indexOf(err.message)) || -1;
12111271
if (pos !== -1)
@@ -1217,7 +1277,7 @@ function formatError(err, constructor, tag, ctx, keys) {
12171277
} else if (ctx.colors) {
12181278
// Highlight userland code and node modules.
12191279
let newStack = stack.slice(0, stackStart);
1220-
const lines = stack.slice(stackStart + 1).split('\n');
1280+
const lines = getStackFrames(ctx, err, stack.slice(stackStart + 1));
12211281
for (const line of lines) {
12221282
const core = line.match(coreModuleRegExp);
12231283
if (core !== null && NativeModule.exists(core[1])) {
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
3+
require('../common');
4+
5+
class FoobarError extends Error {
6+
status = 'Feeling good';
7+
}
8+
9+
const cause1 = new TypeError('Inner error');
10+
const cause2 = new FoobarError('Individual message', { cause: cause1 });
11+
cause2.extraProperties = 'Yes!';
12+
const cause3 = new Error('Stack causes', { cause: cause2 });
13+
14+
process.nextTick(() => {
15+
const error = new RangeError('New Stack Frames', { cause: cause2 });
16+
const error2 = new RangeError('New Stack Frames', { cause: cause3 });
17+
18+
console.log(error);
19+
console.log(cause3);
20+
console.log(error2);
21+
});
+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
RangeError: New Stack Frames
2+
at *
3+
at * {
4+
[cause]: FoobarError: Individual message
5+
at *
6+
at *
7+
... 4 lines matching cause stack trace ...
8+
at * {
9+
status: 'Feeling good',
10+
extraProperties: 'Yes!',
11+
[cause]: TypeError: Inner error
12+
at *
13+
at *
14+
at *
15+
at *
16+
at *
17+
at *
18+
at *
19+
}
20+
}
21+
Error: Stack causes
22+
at *
23+
at *
24+
... 4 lines matching cause stack trace ...
25+
at * {
26+
[cause]: FoobarError: Individual message
27+
at *
28+
at *
29+
... 4 lines matching cause stack trace ...
30+
at *
31+
status: 'Feeling good',
32+
extraProperties: 'Yes!',
33+
[cause]: TypeError: Inner error
34+
at *
35+
at *
36+
at *
37+
at *
38+
at *
39+
at *
40+
at *
41+
}
42+
}
43+
RangeError: New Stack Frames
44+
at *
45+
at * {
46+
[cause]: Error: Stack causes
47+
at *
48+
at *
49+
... 4 lines matching cause stack trace ...
50+
at * {
51+
[cause]: FoobarError: Individual message
52+
at *
53+
at *
54+
... 4 lines matching cause stack trace ...
55+
at * {
56+
status: 'Feeling good',
57+
extraProperties: 'Yes!',
58+
[cause]: TypeError: Inner error
59+
at *
60+
at *
61+
at *
62+
at *
63+
at *
64+
at *
65+
at *
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)