Odd Struct Behavior in CF9
Posted by Euroranger on March 17, 2011
No politics or profanity today. This post, however, is the distilled result of much profanity-inducing code angst…and I simply had to share. Actually, I had already shared this a few weeks back on a tech forum I help out on but figured it couldn’t do any harm to repeat my discoveries here.
It’s not often one runs across something in CF these days that qualifies as an actual bug. This isn’t one (I think) but I encountered this issue recently and wanted to post it up on the web somewhere so when others encounter it Google will have a reference to this post in the appropriate search results.
I’ve recently been tasked with building a centralized site error catcher for my employer. They have dozens of sites running on both CF8 and CF9 and they needed a way to collect all the errors from all the sites and funnel them to one database table collecting things like the site the error occurred on, form/session/URL/client variables in effect at the time of the error and, of course, the error content itself. To do this, I built code in each site’s onError method for each Application.cfc. As we know, the onError method fires for each exception error generated on any sites using that Application.cfc. My plan was to collect the various bits of information I needed, drop them into a Coldfusion struct, serialize that struct into a WDDX string and then store the resultant string in the database. Later, calling up a particular error would require only that you grab the appropriate record from the database, deserialize the string from WDDX back into a struct and then CFDUMP that struct to the screen for the dev to examine. Understand, when building the struct initially, I am storing structs inside the error storage struct that I’m serializing. That is, I’m doing something like this:
<br /> <cfset errorStruct = StructNew()><br /> <cfset temp = StructInsert(errorStruct, "ErrorTime", Now())><br /> <cfset temp = StructInsert(errorStruct, "CGI", cgi)><br /> <cfset temp = StructInsert(errorStruct, "Error", arguments.exception)><br /> <cfset temp = StructInsert(errorStruct, "Form", form)><br /> <cfwddx action="cfml2wddx" input="#errorStruct#" output="errorString"><br />
In case you didn’t know, the onError method in Application.cfc will generate an arguments variable (it is still, after all, a CF component method) called exception that will populate with the error structure that Coldfusion creates whenever an exception error occurs. So, on line 4, I’m dropping that CF generated error struct into my own structure (errorStruct) that I also store the CGI, form and other things in (the example isn’t a line for line copy of what I’m doing).
Anyway, this encountered a problem for some errors wherein the string turned out to be too long (in my case, greater than the 64,000 characters I had allotted via the datatype for that column in the database table). I realized by wholesale saving the entire error scope that I was saving things I didn’t really need: namely things like the StackTrace and objectType items found in the CF generated error struct. My idea was to remove those from the CF generated error struct before storing them in my custom errorStruct…and this is where I encountered the problem I’m writing about.
For reference, I’m using CF9 as illustrated by the dump of the server scope:
And for illustration purposes, I’m going to concentrate solely on StackTrace. The original CF generated error struct looked like this:
So, to get rid of stacktrace my first idea was to do a StructClear which when I tried it in the onError method looked like this:
<cfset temp = StructClear(arguments.exception.stacktrace)>
And resulted in this:
No dice. So my next attempt was a StructDelete:
<cfset temp = StructDelete(arguments.exception,"stacktrace",true)>
Which did this:
Also no joy. So, StructClear and StructDelete both failed. My next thought was to overwrite stacktrace like so:
<cfset arguments.exception.stacktrace = "blorf">
Which, surprisingly, did nothing per the before and after CFDUMPS of the error struct (you should see “blorf” as the stacktrace content in the second dump):
Now, that didn’t overwrite…but it also didn’t generate an error. CF9 simply ignored it. By now, it’s becoming obvious to me that CF9’s error structure is something I’m not allowed to mess with…so naturally, I tried messing with it in one more way: I tried adding to it like so:
<cfset arguments.exception.testy = "this is way testy!">
And got the, by now, predictable result:
It didn’t add it either…but it also didn’t tell me it didn’t. Again, it simply ignored me.
What does all this mean? It means that it appears that CF generated structs like error are pretty much “view only” and cannot be altered. This made me wonder if the same was true for the struct generated when you use CFTRY/CFCATCH and I found it had identical behavior to the error struct that I illustrated above.
In my case, I circumvented this issue by creating my own error struct in the onError method and populating it only with the things I needed before adding it to my overall error struct and serializing it into a string. However, my looking through the online Adobe CF documentation doesn’t mention that either ERROR or CFCATCH structs cannot be edited by application code. What’s worse, the error messages aren’t exactly informative and in cases like editing an existing variable value or adding a new one, it doesn’t tell you anything at all…it simply ignores you which is decidedly unhelpful behavior.
So, consider this post as a warning sign if you need to do more than simply read the contents of either the ERROR or CFCATCH structs generated by CF9. It doesn’t appear to be possible. I do a fair amount of work with OpenBD these days and will be testing this same behavior there and will edit this post with the results from that test when I can.
I welcome comments or contrary test results. Please post them here.