When you see a stack trace on your terminal, it is not some special, magical thing. It is simply nothing but a string representation of an Error
instance. That is, a stack trace is nothing but the output of the toString
method of that Error
instance.
When you create an Error
instance, an object is created with the following properties which has the following attributes. Put the following in a file named script.js
and run it with node script.js
:
const e = Error('Some optional message');
console.log(getAllProperties(e));
// This function is from https://gist.github.com/ugultopu/15abb1790b3d08384a372cdef3ab5b81
function getAllProperties(object) {
// Same as:
// if (object === null || object === undefined) return;
if (object == null) return;
const properties = {...Object.getOwnPropertyDescriptors(object)};
let currentObject = properties;
while (object = Object.getPrototypeOf(object)) {
currentObject.PROTO = Object.getOwnPropertyDescriptors(object);
currentObject = currentObject.PROTO;
}
return properties;
}
Output:
{
stack: {
value: 'Error: Some optional message\n' +
' at Object.<anonymous> (/path/to/the/script.js:1:11)\n' +
' at Module._compile (internal/modules/cjs/loader.js:1063:30)\n' +
' at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)\n' +
' at Module.load (internal/modules/cjs/loader.js:928:32)\n' +
' at Function.Module._load (internal/modules/cjs/loader.js:769:14)\n' +
' at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)\n' +
' at internal/main/run_main_module.js:17:47',
writable: true,
enumerable: false,
configurable: true
},
message: {
value: 'Some optional message',
writable: true,
enumerable: false,
configurable: true
},
PROTO: {
constructor: {
value: [Function],
writable: true,
enumerable: false,
configurable: true
},
name: {
value: 'Error',
writable: true,
enumerable: false,
configurable: true
},
message: {
value: '',
writable: true,
enumerable: false,
configurable: true
},
toString: {
value: [Function: toString],
writable: true,
enumerable: false,
configurable: true
},
PROTO: {
constructor: [Object],
__defineGetter__: [Object],
__defineSetter__: [Object],
hasOwnProperty: [Object],
__lookupGetter__: [Object],
__lookupSetter__: [Object],
isPrototypeOf: [Object],
propertyIsEnumerable: [Object],
toString: [Object],
valueOf: [Object],
__proto__: [Object],
toLocaleString: [Object]
}
}
}
As you can observe, the Error
instance has two properties, stack
and message
. message
is the argument that we provide to the invocation of the Error
constructor (Error
function). Whereas stack
is a property that is "auto-generated" (auto-populated) using:
- The
message
property (that is, the argument that you pass to theError
constructor). - Current state of the call stack.
The stack
property is nothing but a regular (normal) string. The composition of this string is as follows:
- Name of the error. In this case,
Error
. If we were using another type of error, name would be that. - Followed by a colon and a space (that is, "
:
"). - Followed by the message that has been passed to the
Error
"constructor" function. - Followed by a string representation of the state of the call stack at the time that the
Error
instance was created.
Note that the enumerable
attribute of both the stack
property, and the message
property is false
. This means that if we were to print the properties of this Error
instance using, say a for-in loop, we wouldn't see neither of these properties because they are not enumerable (i.e, their enumerable
attribute is false
).
After this information, let's display an Error
instance on our terminal. We can display it with the console.log
function, which simply calls the toString
method on its argument and prints the result on the terminal. Start the Node.js REPL by running the node
command on your terminal and enter the following code:
const e = Error('Some optional message');
console.log(e);
Output:
Error: Some optional message
at REPL1:1:11
at Script.runInThisContext (vm.js:133:18)
at REPLServer.defaultEval (repl.js:484:29)
at bound (domain.js:413:15)
at REPLServer.runBound [as eval] (domain.js:424:12)
at REPLServer.onLine (repl.js:817:10)
at REPLServer.emit (events.js:327:22)
at REPLServer.EventEmitter.emit (domain.js:467:12)
at REPLServer.Interface._onLine (readline.js:337:10)
at REPLServer.Interface._line (readline.js:666:8)
Let's add a property to the error and display it:
e.someProp = 'some value';
console.log(e);
Output:
Error: Some optional message
at REPL1:1:11
at Script.runInThisContext (vm.js:133:18)
at REPLServer.defaultEval (repl.js:484:29)
at bound (domain.js:413:15)
at REPLServer.runBound [as eval] (domain.js:424:12)
at REPLServer.onLine (repl.js:817:10)
at REPLServer.emit (events.js:327:22)
at REPLServer.EventEmitter.emit (domain.js:467:12)
at REPLServer.Interface._onLine (readline.js:337:10)
at REPLServer.Interface._line (readline.js:666:8) {
someProp: 'some value'
}
When you add a property to an object, by default, the enumerable
attribute of that property is true
. Hence, the new property is present in the print-out of the error instance (which is simply the output of calling the toString
method of the Error
instance) in the form of {propertyName: propertyValue}
which follows the value of the stack property.
Hence, with the presence of at least one enumerable: true
property on the Error
instance, the format of the print-out becomes:
- The value of the
stack
property. - Followed by a pair of curly braces that contains the name and value of the properties of the
Error
instance whoseenumerable
attributes aretrue
.
Let's add another property:
e.anotherProp = 'another value';
console.log(e);
Output:
Error: Some optional message
at REPL1:1:11
at Script.runInThisContext (vm.js:133:18)
at REPLServer.defaultEval (repl.js:484:29)
at bound (domain.js:413:15)
at REPLServer.runBound [as eval] (domain.js:424:12)
at REPLServer.onLine (repl.js:817:10)
at REPLServer.emit (events.js:327:22)
at REPLServer.EventEmitter.emit (domain.js:467:12)
at REPLServer.Interface._onLine (readline.js:337:10)
at REPLServer.Interface._line (readline.js:666:8) {
someProp: 'some value',
anotherProp: 'another value'
}
The new property is present in the print-out as well.
Now, let's try changing the value of the stack
property of the Error
instance. The enumerable
attribute of the stack
property is false
. However, the writeable
attribute of the stack
property is not false
. It is true
. Hence, we can change the value of the stack
property:
e.stack = 'asdf';
console.log(e);
Output:
[asdf] { someProp: 'some value', anotherProp: 'another value' }
As you can observe, the print-out changed, because we have changed the value of the stack
property. However, note one difference from the format of the previous print-out. In the previous print-out, the value of the stack
property was followed by a pair of curly braces that contains the name and value of the properties of the Error
instance whose enumerable
attributes were true
. This time, everything is the same, except that the value of the stack
property is surrounded by square brackets.
Let's see if we can get the print-out format to be like the previous print-out format again (that is, value of stack
property is not surrounded with square brackets):
e.stack = 'asdf asdf';
console.log(e)
Output:
[asdf asdf] { someProp: 'some value', anotherProp: 'another value' }
e.stack = 'asdf\nasdf';
console.log(e)
Output:
[asdf
asdf] {
someProp: 'some value',
anotherProp: 'another value'
}
e.stack = 'asdf\n asdf';
console.log(e)
Output:
[asdf
asdf] {
someProp: 'some value',
anotherProp: 'another value'
}
e.stack = 'asdf\n atsdf';
console.log(e)
Output:
asdf
atsdf {
someProp: 'some value',
anotherProp: 'another value'
}
After some trial-and-error as shown above, we have discovered that if the value of the stack
property (which is nothing but a regular string), contains a newline, followed by 4 spaces, followed by the characters a
and t
, then the portion of the print-out that represents the stack
property is not wrapped in square brackets. Otherwise, it is.
The code that causes this is the following. It is located in the formatError
function which is in lib/internal/util/inspect.js
in Node.js source code.
// Wrap the error in brackets in case it has no stack trace.
const stackStart = stack.indexOf('\n at', pos);
if (stackStart === -1) {
stack = `[${stack}]`;
}