A reader posted an interesting comment to my ColdFusion Exception Handling Guide. He had modified his error handling to store the errors in a database. This allowed him to look at history exception information, do trending, etc. But he ran into trouble trying to remove the stack trace from the exception object. Here is an example of that.
Imagine a simple onError like so:
2 <cfargument name="exception" required="true">
3 <cfargument name="eventname" type="string" required="true">
4 <cfset structDelete(arguments.exception, "stacktrace")>
5 <cfdump var="#exception#">
6 <cfabort>
7 </cffunction>
Doing this causes the error handler itself to barf out: cannot remove properties from a java object. While I knew that Exceptions were Java objects, I had always assumed that when ColdFusion got to it, it was a normal struct. When you cfdump it, you see a struct, which is very different from the normal Java object dump. However, you can see that it is not if you check the class name:
This returns coldfusion.runtime.UndefinedVariableException whereas a "real" structure would return coldfusion.runtime.Struct. Ok, so this implies that cfdump recognizes the ColdFusion exception and massages the output a bit. What happens if we try to duplicate the structure?
2 <cfset structDelete(ex, "stacktrace")>
3 <cfdump var="#ex#">
Unfortunately this returns the exact same error: cannot remove properties from a java object. So we still have a Java object after the duplicate. No surprise there I guess, but if cfdump had a 'hack' for ColdFusion exceptions I thought perhaps duplicate might.
I then tried this variation:
2 <cfloop item="key" collection="#arguments.exception#">
3 <cfset newEx[key] = duplicate(arguments.exception[key])>
4 </cfloop>
5
6 <cfdump var="#newEx#">
7 <cfdump var="#newex.getClass().getName()#">
And bam - that did it. So at the key level the values came over correctly. And just to be sure, I then did this:
And bam - that worked perfectly.
Of course, this may be overkill. If you are inserting the values from the exception object into the database, you can simply do the left in your cfquery. So for example, this is fine:
I'm not modifying the actual Exception object, just the result of getting the string value.


Comment 1 written by Paul Dynan on 17 September 2009, at 9:52 AM
I did notice a caveat: The structs inside of Error remain as java objects (discovered while tryign to delete Error.RootCause.StackTrace).
I'm assuming I could just not add RootCause on the original copy, and loop it in using your method as an update after to further trim. Or, when I hit that key during the original copy, do an inner loop to build the contents of the key there.
I all probably seems excessive, but on just a query error, there four stacks and makes the error considerably longer than it needs to be. I feel it's akin to clearing a stack of papers off your desk, rather than just working around it all day.
Comment 2 written by Raymond Camden on 17 September 2009, at 10:25 AM
Comment 3 written by Paul Dynan on 17 September 2009, at 10:40 AM
<CFSET newError = structNew()>
<CFLOOP ITEM="key" COLLECTION="#Error#">
<CFIF key IS NOT "StackTrace">
<cfset newError[key] = duplicate(Error[key])>
</CFIF>
<CFIF key IS "RootCause">
<CFSET RootCause = structNew()>
<CFLOOP ITEM="key1" COLLECTION="#Error.RootCause#">
<CFIF ListFind("Cause,RootCause", key1) GT 0>
<CFSET temp = structNew()>
<CFLOOP ITEM="key2" COLLECTION="#Error.RootCause["#key1#"]#">
<CFIF key2 IS NOT "StackTrace">
<CFSET temp[key2] = duplicate(Error.RootCause["#key1#"][key2])>
</CFIF>
</CFLOOP>
<CFSET RootCause[key1] = temp>
<CFELSEIF key1 IS NOT "StackTrace">
<CFSET RootCause[key1] = duplicate(Error.RootCause[key1])>
</CFIF>
</CFLOOP>
<CFSET newError[key] = RootCause>
</CFIF>
</CFLOOP>
Comment 4 written by todd sharp on 17 September 2009, at 10:41 AM
Comment 5 written by Raymond Camden on 17 September 2009, at 10:44 AM
Comment 6 written by Raymond Camden on 17 September 2009, at 10:45 AM
<cfset var s = serializeJSON(arguments.exception)>
<cfset var newEx = deserializeJSON(s)>
This gives you a vanilla struct. So you can manipulate as you will.
Comment 7 written by Paul Dynan on 17 September 2009, at 11:17 AM
<CFSET tempError = serializeJSON(Error)>
<CFSET newError = deserializeJSON(tempError)>
<CFLOOP LIST="newError,newError.RootCause,newError.RootCause.Cause,newError.RootCause.RootCause" INDEX="i">
<CFIF StructKeyExists( Evaluate(i), "StackTrace" )>
<CFSET temp = StructDelete( Evaluate(i), "StackTrace")>
</CFIF>
</CFLOOP>
[Add Comment] [Subscribe to Comments]