Error Object Properties Are Not Iterable / Enumrable In Node.js
Over the weekend, I was playing around with custom Node.js errors and error logging when I noticed something weird: none of the ".stack" properties (ie, the stack traces) were showing up in my log files. After a bit more experimentation, I realized that none of the core Error properties were enumerable; as such, none of the core Error properties were being copied during extend and stringify style operations. To fix this, I had to start checking for those properties explicitly when logging errors.
Before I look at what I did to copy the properties, let's first confirm that the properties, in which we are interested, are not enumerable. To do this, we can get the property descriptors from the Error instance using Object.getOwnPropertyDescriptor():
// Create an error object.
var e = new Error( "Something went wrong." );
// Check the property descriptors to see how they are defined.
console.log( Object.getOwnPropertyDescriptor( e, "message" ) );
console.log( Object.getOwnPropertyDescriptor( e, "stack" ) );
When we run this code (node version v0.12.2), we get the following terminal output:
{
. . . . value: 'Something went wrong.',
. . . . writable: true,
. . . . enumerable: false,
. . . . configurable: true
}
{
. . . . get: [Function],
. . . . set: [Function],
. . . . enumerable: false,
. . . . configurable: true
}
As you can see, both the "message" and the "stack" properties have "enumerable" set to false. As such, they won't show up in Object.keys(), for..in loops, or JSON.stringify() operations. To get around this, in my logging, I resorted to checking for these properties explicitly. And, while this does somewhat couple the logging code to the implementation of the Error object, I felt like this was still an appropriate concern of the logging mechanism:
// Cause an error and then log it.
try {
var foo = bar;
} catch ( error ) {
// NOTE: Unlike the core Error properties, this one will be iterable.
error.customProperty = "Injected custom property.";
logError(
{
message: "Something went wrong when trying to set Foo!"
},
error
);
}
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
// Create a log-entry and log it to the console (for demo).
function logError( entry, error ) {
// Create a log-entry with the error data as a key off the entry.
var data = extend(
{
_id: ( process.pid + "-" + Date.now() ),
_timestamp: ( new Date() ).toUTCString(),
},
entry,
{
error: extend( {}, error )
}
);
// None of the native error objects properties are iterable. As such, we have to
// explicitly check for error-specific properties that we want to track for future
// debugging purposes.
// --
// NOTE: If the Error object is a custom error object, it might have other
// properties, but those will be handled implicitly by the extend() call above.
[ "name", "message", "stack" ].forEach(
function iterator( key ) {
if ( error[ key ] && ! data.error[ key ] ) {
data.error[ key ] = error[ key ];
}
}
);
// For demo, log to console.
console.error( data );
}
// I collapse the given argument list down into the destination object and return it.
function extend( destination, source ) {
for ( var i = 1, length = arguments.length ; i < length ; i++ ) {
var source = arguments[ i ];
for ( var key in source ) {
if ( source.hasOwnProperty( key ) ) {
destination[ key ] = source[ key ];
}
}
}
return( destination );
}
As you can see, after I create my log entry object, I loop over targeted Error properties and perform an explicit check-and-copy if they haven't been moved over during the extend() operation. When I log out the entry item, it looks something like this:
{
. . . . _id: '4712-1437995948390',
. . . . _timestamp: 'Mon, 27 Jul 2015 11:19:08 GMT',
. . . . message: 'Something went wrong when trying to set Foo!',
. . . . error: {
. . . . . . . . customProperty: 'Injected custom property.',
. . . . . . . . name: 'ReferenceError',
. . . . . . . . message: 'bar is not defined',
. . . . . . . . stack: 'ReferenceError: bar is not defined ... at Object.<anonymous>'
. . . . }
}
There are other means of iterating over non-enumerable properties (ex, Object.getOwnPropertyNames()); but, I don't want to lose sight of the intent of object property descriptors. More often than not, properties are non-enumerable for a reason. It just so happens that in this very specific context - error logging - there are a few non-enumerable properties that I really want to access.
Want to use code from this post? Check out the license.
Reader Comments
You said:
"More often than not, properties are non-enumerable for a reason."
What could that reasoning be for these specific properties? Any guesses?
@JC,
For *these specific* properties... NO idea :D I can't think of any reason why I wouldn't want those to be part of the natural key collection. Seems like an odd choice to me.
I've never cared much for dealing with JavaScript's prototypal inheritance chain for native objects... For instance, to do something simple like extending Error for argument validation, a lot of imperative, boilerplate code is required...
Here's a naive implementation...
Let's say we want to extend Error to have an InvalidArgumentException handler... In order to do that... we need to not only declare our implementation method as below...
//Implementation
function InvalidArgumentException( message){
this.message = message;
if("captureStackTrace" in Error) {
Error.captureStackTrace( this, InvalidArgumentException );
} else {
this.stack = (new Error()).stack;
}
}
but we also have to explicitly declare the inherited properties from the parent... I.E.
//Explicit member inheritance declaration required...
InvalidArgumentException.prototype = Object.create( Error.prototype );
InvalidArgumentException.prototype.name = "InvalidArgumentException";
InvalidArgumentException.prototype.constructor = InvalidArgumentException;
I dunno... it just feels like a major flaw in the language design...
(Here's a simple gist of all the code above...) http://bit.ly/1D4Y40f
@Edward,
Yeah, there's definitely a good degree of boilerplate for things like this. I believe that ES6 makes that stuff a bit easier; though, I can't speak from experience. In reality, I don't use sub-classing all that much in JavaScript. Really, I only use it in Node.js for a few things, typically related to Streams. When I can, I try to favor composition over inheritance.