Experimenting With ExtendedInfo To Aggregate Error Information In ColdFusion

For the last few weeks, I've had the article, Java exception handling best practices by Lokesh Gupta, open in one of my browser tabs. While I don't program in Java, I think the article has a number of really thought-provoking points about proper error handling in any kind of application, regardless of language. The suggestions that stick out to me are:

6) Either log the exception or throw it but never do the both.

7) Never throw any exception from finally block.

8) Always catch only those exceptions that you can actually handle.

10) Use finally blocks instead of catch blocks if you are not going to handle exception.

11) Remember "Throw early catch late" principle.

13) Throw only relevant exception from a method.

17) Pass all relevant information to exceptions to make them informative as much as possible.

While some of these are about workflow, one theme that occurs to me is: more data equals easier debugging. In JavaScript, when you create an Error object and throw it, the error object can include any kind of data that you want. But, in ColdFusion, we're a bit more limited. Unless you start creating custom Java Exception objects, we have access to the throw() operator, which only allows for a few string-based fields.

Thinking back to my experiment with "Russian Doll" error reporting in Node.js, however, I wondered if the same sort of approach could be used in ColdFusion, albeit with a little more elbow-grease. In ColdFusion, the throw() operator accepts an extendedInfo property. This property has to be a string; but, in ColdFusion, we can easily serialize complex objects into strings. So, I wanted to see what it would look like to try and use the extendedInfo property to nest errors as you move up the call-stack.

The following demo is completely trite; so, try not to get distracted by the demo itself, but rather on the mechanics involved. I have a method that throws an error. The calling context then catches that error, wraps it, and throws another error, this time including any additional context information that may be relevant for debugging:

// Now that we've traveled down the object tree, dump out the top-level

// error object.

writeDump( outerError );

}

// I coerce the given ColdFusion error object into a ColdFusion struct.

public struct function errorToStruct( required any error ) {

var errorAsData = {};

structAppend( errorAsData, error );

return( errorAsData );

}

</cfscript>

As you can see, as the error moves up the call-stack, it is caught, serialized as JSON (JavaScript Object Notation), and added to a new error as the "extendedInfo" property. The new error, therefore, contains the root error as well as any other information that might be relevant for debugging.

When the error finally bubbles up to the top-level exception handler, it is dumped out. For the purposes of easy consumption, my dump operation traverses the error object and deserializes the extendedInfo fields so that they can be easily read when output:

As you can see, the top-level error object becomes a "Russian doll", so to speak, of error data as the original throw travels up the call-stack.

Even if you think this idea is crazy, maybe you can still see some value in using the "extendedInfo" property to capture additional data about an error context. I have found this particularly helpful when dealing with external APIs that communicate over HTTP. Imagine that you have to communicate with an HTTP API and it returns a 500 status code. Typically, in that situation, I'll throw a UnexpectedError error; but, I'll often use the extendedInfo property to include information about why the HTTP request failed (like its response body). This has been super helpful for debugging.

The more I think about the above list of error handling practices, the more I think about the importance of this one:

Either log the exception or throw it but never do the both.

Being able to nest error objects makes this possible while, at the same time, not losing fidelity of information. Anyway, just some noodling on error handling in ColdFusion. Think of this as an experiment and not necessarily a suggestion.

Reader Comments

The one problem with using serializeJSON() is that serialization bugs could result in the "catch" being transformed in some ways that might end up being misleading when debugging. It might be better to serialize the catch using ByteArrayOutputStream, then store it as Base64 (see https://www.petefreitag.com/item/649.cfm for an example).

This should guarantee that the object isn't altered in any subtle ways that could lead to confusion.

My first impression of this is... yes! I'm definitely going to play with this approach and have a couple places in mind straight off that I think this will be helpful in my codebase. I also appreciate Dan chiming in with his suggestion to maintain the integrity of the data structure. I'd never thought to convert to a byteArray then b64 the stream. Also worthy of experimentation. You two have inspired me today, and I appreciate that! Thanks!

Really interesting. I don't think that I knew that was even possible. I know that there are objectSave() and objectLoad() methods in ColdFusion - I wonder if they are using this API under the hood. I've experimented with them a tiny bit; but, never really found a use-case for them in my limited experience.

That said, since this is for debugging purposes, the 100% fidelity of the JSON serialization life-cycle might not be a deal breaker. Meaning, if something were to serialize "true" to be "YES" (for example), the subsequent log item would still be human-consumable.

Of course, how tired is everyone of the fact that JSON in ColdFusion doesn't just "work".... like it does is like every other language :(

I am the co-founder and lead engineer at InVision App, Inc — the world's leading prototyping,
collaboration & workflow platform. I also rock out in JavaScript and ColdFusion 24x7 and I dream about
promise resolving asynchronously.