A tutorial on how to get ANY ColdFusion error emailed to you automatically and a friendly message displayed to the visitor would be great, I found one on EasyCFM.com but doesn't actually work.
Having looked at the EasyCFM tutorial, it looks like you want to know about how to use ColdFusion's CFError tag. Before I go into this, I have to just say that if you are using ColdFusion 7 or greater, I would recommend moving to the Application.cfc model and use the OnError() application event method handler; it just provides a nice, clean way of doing this. But that is a whole other discussion. For now, I will just cover the use of the ColdFusion CFError tag and how it can be used for error handling.
To start off with, we have to put the ColdFusion CFError tag on a template that will be executed for every page request such that every new page request will know how to properly handle errors. The obvious choice is to use the Application.cfm template:
Launch code in new window » Download code as text file »
As you can see here, with the ColdFusion CFError tag we are telling it to handle "Exception" based errors with the template, "cferror.cfm". The template path that you want to use with this tag is relative to the Application.cfm file itself; it does not matter which page in the application actually throws the root error - like the ColdFusion CFInclude tag, the path is relative to the tag-defining template not the page template.
Also, if you look at the documentation, you will see that you can define more that one CFError tag - one for each of the possible error types. Don't worry about this. You pretty much will never do that. Stick to Exception. The others no longer serve any real purpose. In fact, if you are using the other types, you might want to reconsider what you are doing (god forbid you are using the built-in ColdFusion data validation!!!).
The template that we are pointing to, cferror.cfm, is not really special in any way. It's just a ColdFusion template like any other. The only real difference is that if the template gets included by the CFError tag, a new variable is created: VARIABLES.Error. This Error object contains the information about the exception that was thrown by the code. This will include things like the stack trace, tag context, message, detail, and template and line number where the error occurred.
Other than that, the only other thing you have to worry about is whether or not content has already been flushed to the browser. If the exception was thrown before the content buffer started flushing, then the CFError template has a blank slate to work with. If, however, content has already been flushed to the browser at the time of the error, then the CFError template will already have a partial page displayed. If one of the goals here is to show a nice "Error Page" to people, then this is something to be weary of. In the following page, you will see that we try to set some header values. If that fails (indicating that content has already gone to the client), we will redirect the user back to the error page explicitly so that we can start a new page. Of course, either way, we want to mail the error to someone.
Here it my example cferror.cfm ColdFusion template:
Launch code in new window » Download code as text file »
A few minor things to notice in the above template. First, we are checking to see if the variable Error exists in the VARIABLES scope. If it does, then we are sending out the error email. This will stop us from trying to reference the Error object if the cferror.cfm page was called directly. Also notice that when we CFDump out our relevant scopes, we have two things going on:
The TOP attribute of the ColdFusion CFDump tag is VERY awesome. It limits the depth of recursion when dumping out the contents of the given variables. It also limits query and array dumping, but that's not what's so cool at this moment. If you ever have a struct that has circular references (like a bi-directional linked list), your CFDump will possibly crash the server because it never knows when to stop. By telling the CFDump tag to stop at 5 levels, even if you have circular references, it will limit the recursive depth to 5 - wicked sweet!
The MakeStructSecure() ColdFusion user defined function is a method that I built based on a tip I got from Tobe Goldfinger of the New Your ColdFusion User group. She was saying that when we mail ourselves error information, often times we forget that it might contain secure information such as credit card numbers and expiration dates. Obviously, we don't want to be sending that information out over the unsecured email pathways, so this MakeStructSecure() recursively searches through the given struct looking for keys that look suspicious so that it can black out the values:
Launch code in new window » Download code as text file »
Here, this ColdFusion user defined function is looking for the following keys:
Now, this can work, but if you KNOW that one of your structs (ex. the FORM scope) might have secure information, I would check for this explicitly. Do NOT rely on this function to actually work. I put it in more to have a little fun and to demonstrate that we need to be actively thinking about this kind of stuff.
Right before we display the error page, we try to set some header information using the CFHeader / CFContent tag. If this works, then we know that we have a blank page to work with. However, if this fails (you cannot set header information on a page response that has already been committed to the browser), it means that some of the page content has already been flushed. If that is the case, we are using Javascript to immediately forward the user (browser refresh) to the cferror.cfm page. At this point, the CFMail has already been sent out, we just care about proper display. Notice that when we refresh the browsr, we are passing along a flag so the cferror.cfm template knows not to do that again if asked to (this will prevent infinite forwarding).
Note: There are other ways to check to see if the request has already been committed, but I am trying to keep this a bit more low-level.
The last thing left is just to throw an error to see this in action :) In our index.cfm file, we are going to refer to an undefined variable:
Launch code in new window » Download code as text file »
Just to demonstrate that the MakeStructSecure() method is actually doing something, I have thrown some credit card information into the FORM scope. Anyway, throwing the error above provides the user friendly error page and sends out the email. The email that we get looks like this:
| | | | ||
| | ![]() | | ||
| | | |
Notice that we get our nicely formatted HTML email and that the secure FORM data has been escaped.
Hope this helped a bit or at least pointed you in the right direction.
Download Code Snippet ZIP File
Comments (9) | Post Comment | Ask Ben | Permalink | Other Searches | Print Page
Great work Ben,
I implemented a similar concept into my app just a moment ago, using the onError() method from my application.cfc, I've already spotted and fixed 5 bugs that I wasnt aware of as they existed in an old web service which isnt used for user iteraction, so there was no one to report the error to me :-D
Great stuff mate, really great stuff, I'd been thinking about doing this for a long while, just needed a firm kick in the pants to get it done.
*thumbs up
Rob
Posted by Rob Rawlins on Aug 29, 2007 at 10:10 AM
@Rob,
Thanks a lot. Also, since you mentioned OnError(), I just wanted to mention to other people that OnError() is great because it is a very clean way to integrate with the application level events, but at the same time, most of the same issues are still present (partial page flushing, potentially undefined error object).
Glad to see error handling is working out so nicely for you :)
Posted by Ben Nadel on Aug 29, 2007 at 10:22 AM
lots of great info in there, Ben. Thanks for the thorough explanations! How do you have time to get actual work done? Does your job mind you spending time blogging during work hours?
Anyways, here's a custom tag I worked up over the years, though I use onError() now in CF 7 and 8:
http://cfzen.instantspot.com/blog/index.cfm/2007/5/4/heres-my-errorHandlercfm-what-do-you-think
(it's open for comment)
cheers.
Posted by Aaron Longnion on Aug 29, 2007 at 10:28 AM
Some good ideas in here Ben! You might want to also consider removing the CFID/CFTOKEN since theoretically someone could use those to hijack another user's session, and possibly view secure information, depending on your application. With my errors, I took the approach of writing the full error dumps out to a protected .cfm file and just email the admin a summary of the error. The full error pages are viewable through an admin login area and SSL.
Posted by Mary Jo on Aug 29, 2007 at 11:23 AM
now if the error was actually a database error and you wanted to see the sql, you could just add a cfif section for the error type?
Posted by michael white on Aug 29, 2007 at 12:06 PM
@Michael
If you are catching db errors you'll output these:
<cfif IsDefined("catch.SQLState")>
SQL State: #catch.SQLState#
</cfif>
<cfif IsDefined("catch.Sql")>
SQL: #catch.Sql#
</cfif>
<cfif IsDefined("catch.queryError")>
Query Error: #catch.queryError#
</cfif>
<cfif IsDefined("catch.where")>
Where :#catch.where#
</cfif>
Posted by Dustin on Aug 29, 2007 at 1:02 PM
Has anyone found a way to dump the local var scope in a CFC? It doesn't seem that ColdFusion puts them into any named scope which makes it really frustrating to track down an intermittent error in a CFC.
Posted by Mary Jo on Aug 29, 2007 at 3:50 PM
@Mary Jo,
I can't seem to find the link right now, but I recently read a blog post about someone who got the "Active Local Scope" from the Page Context object.
GetPageContext().getActiveFunctionLocalScope()
Give that a go.
Posted by Ben Nadel on Sep 9, 2007 at 6:07 PM
Thanks Ben, looks like you blogged about all the GetPageContext stuff awhile back. ;-)
http://www.bennadel.com/blog/758-ColdFusion-GetPageContext-Massive-Exploration.htm
It does seem that the local scope still gets destroyed before going to a site-wide error handler (which I guess you would expect but what I'd really like to find a way around), but at least this will give me a tool when I need to remotely debug something in a CFC.
Posted by Mary Jo on Sep 9, 2007 at 10:08 PM