Ask Ben: Handling Errors With ColdFusion CFError

Posted August 29, 2007 at 8:23 AM

Tags: ColdFusion, Ask Ben

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 »

  • <!--- Kill extra output. --->
  • <cfsilent>
  •  
  • <!--- Define the application settings. --->
  • <cfapplication
  • name="ErrorHandlingDemo"
  • applicationtimeout="#CreateTimeSpan( 0, 0, 1, 0 )#"
  • sessionmanagement="true"
  • sessiontimeout="#CreateTimeSpan( 0, 0, 1, 0 )#"
  • setclientcookies="true"
  • />
  •  
  • <!--- Define the page request settigs. --->
  • <cfsetting
  • requesttimeout="20"
  • showdebugoutput="false"
  • />
  •  
  • <!--- Define erorr handling template. --->
  • <cferror
  • type="exception"
  • template="./cferror.cfm"
  • />
  •  
  • <!--- Include functions. --->
  • <cfinclude template="functions.cfm" />
  •  
  • </cfsilent>

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 »

  • <!--- Kill extra output. --->
  • <cfsilent>
  •  
  • <!---
  • Check to see if the error object exists. Even though
  • we have landed on this page, it is possible that
  • someone called it directly without throwing an erorr.
  • The error object only exists if an error was caught.
  • --->
  • <cfif StructKeyExists( VARIABLES, "Error" )>
  •  
  • <!---
  • Mail out the error data (and whatever other scopes
  • you would like to see at the time of th error). When
  • you CFDump out the objects, make them Secure AND
  • also be sure to use a TOP attribute when appropriate
  • so that the CFDump doesn't recurse forever.
  • --->
  • <cfmail
  • to="ben@xxxxxxxxxx.com"
  • from="web-errors@xxxxxxxxxx.com"
  • subject="Web Site Error"
  • type="html">
  •  
  • <p>
  • An error occurred at
  • #DateFormat( Now(), "mmm d, yyyy" )# at
  • #TimeFormat( Now(), "hh:mm TT" )#
  • </p>
  •  
  • <h3>
  • Error
  • </h3>
  •  
  • <cfdump
  • var="#MakeStructSecure( VARIABLES.Error )#"
  • label="Error object."
  • />
  •  
  • <h3>
  • CGI
  • </h3>
  •  
  • <cfdump
  • var="#MakeStructSecure( CGI )#"
  • label="CGI object"
  • />
  •  
  • <h3>
  • REQUEST
  • </h3>
  •  
  • <cfdump
  • var="#MakeStructSecure( REQUEST )#"
  • label="REQUEST object"
  • top="5"
  • />
  •  
  • <h3>
  • FORM
  • </h3>
  •  
  • <cfdump
  • var="#MakeStructSecure( FORM )#"
  • label="FORM object"
  • top="5"
  • />
  •  
  • <h3>
  • URL
  • </h3>
  •  
  • <cfdump
  • var="#MakeStructSecure( URL )#"
  • label="URL object"
  • top="5"
  • />
  •  
  • <h3>
  • SESSION
  • </h3>
  •  
  • <cfdump
  • var="#MakeStructSecure( SESSION )#"
  • label="SESSION object"
  • top="5"
  • />
  •  
  • </cfmail>
  •  
  • </cfif>
  •  
  •  
  •  
  • <!---
  • When setting the header information, be sure to put
  • it in a CFTry / CFCatch. We can only send header
  • information if the site has NOT already been flushed
  • to the browser. Also set a flag so that we know if
  • information has been committed.
  • --->
  • <cfset REQUEST.RequestCommitted = false />
  •  
  • <cftry>
  • <!--- Set the status code to internal server error. --->
  • <cfheader
  • statuscode="500"
  • statustext="Internal Server Error"
  • />
  •  
  • <!--- Set the content type. --->
  • <cfcontent
  • type="text/html"
  • reset="true"
  • />
  •  
  • <!--- Catch any errors. --->
  • <cfcatch>
  •  
  • <!---
  • There was an error so flag the request as
  • already being committed.
  • --->
  • <cfset REQUEST.RequestCommitted = true />
  •  
  • </cfcatch>
  • </cftry>
  •  
  • </cfsilent>
  •  
  • <!---
  • Check to see if the request has been committed. If it
  • has, then it means that content has already been committed
  • to the browser. In that case, we are gonna want to refresh
  • the screen, unless we came from a refresh, in which case
  • just let the page run.
  • --->
  • <cfif (
  • StructKeyExists( VARIABLES, "Error" ) AND
  • REQUEST.RequestCommitted AND
  • (NOT StructKeyExists( URL, "norefresh" ))
  • )>
  •  
  • <script type="text/javascript">
  •  
  • window.location.href = "cferror.cfm?norefresh=true";
  •  
  • </script>
  •  
  • <!--- Exit out of the template. --->
  • <cfexit />
  •  
  • </cfif>
  •  
  •  
  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>An Error Occurred</title>
  • </head>
  • <body>
  •  
  • <h1>
  • Internal Server Error
  • </h1>
  •  
  • <p>
  • An internal server error has occurred, but our
  • squad of CF Ninjas are looking into it! Naturally,
  • you won't be able to tell that they're doing
  • anything because they are Ninjas! But, rest assured,
  • stuff is getting done.
  • </p>
  •  
  • </body>
  • </html>

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:

  1. We are using the TOP attribute.
  2. We are using the UDF, MakeStructSecure().

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 »

  • <!---
  • Define function that will recursively search a
  • struct and it's nested elements for keys that should
  • be considered secure and blacked out.
  • --->
  • <cffunction
  • name="MakeStructSecure"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Does a very cursory job of cleaning up a struct by blacking out secure information.">
  •  
  • <!--- Define argumets. --->
  • <cfargument
  • name="Struct"
  • type="struct"
  • required="true"
  • hint="The struct we are going to clean."
  • />
  •  
  • <cfargument
  • name="Depth"
  • type="numeric"
  • required="false"
  • default="1"
  • hint="The depth of the current search - this will stop the function from looping infinitely."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />
  •  
  • <!---
  • Check to see if we have reached our max depth
  • for this search.
  • --->
  • <cfif (ARGUMENTS.Depth GTE 5)>
  •  
  • <!---
  • You're going too deep, it hurts! We might be
  • looping in a circular struct reference.
  • --->
  • <cfreturn />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • ASSERT: If we have made it this far, then we have
  • to check all the struct keys at this level.
  • --->
  •  
  • <!---
  • Define the list of keys that would be considered
  • to hold secure data.
  • --->
  • <cfsavecontent variable="LOCAL.SecureKeys">
  • CreditCard
  • CCNumber
  • CCNum
  • ExpirationDate
  • Expry
  • ExpDate
  • CCExp
  • </cfsavecontent>
  •  
  •  
  • <!---
  • Loop over the struct looking for keys that would
  • flag secure data to be removed.
  • --->
  • <cfloop
  • item="LOCAL.Key"
  • collection="#ARGUMENTS.Struct#">
  •  
  • <!---
  • Check to see if the key is to be considered
  • secure AND that the value in it is simple.
  • --->
  • <cfif (
  • FindNoCase( LOCAL.Key, LOCAL.SecureKeys ) AND
  • IsSimpleValue( ARGUMENTS.Struct[ LOCAL.Key ] )
  • )>
  •  
  • <!--- Black out value. --->
  • <cfset ARGUMENTS.Struct[ LOCAL.Key ] = RepeatString(
  • "*",
  • Len( ARGUMENTS.Struct[ LOCAL.Key ] )
  • ) />
  •  
  • <!---
  • Check to see if this key is a struct that we
  • might have to search through.
  • --->
  • <cfelseif IsStruct( ARGUMENTS.Struct[ LOCAL.Key ] )>
  •  
  • <!---
  • Recurse through this one. Be sure to send
  • through a new depth value so we don't loop
  • forever.
  • --->
  • <cfset MakeStructSecure(
  • Struct = ARGUMENTS.Struct[ LOCAL.Key ],
  • Depth = (ARGUMENTS.Depth + 1)
  • ) />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  •  
  • <!--- Return out. --->
  • <cfif (ARGUMENTS.Depth EQ 1)>
  •  
  • <!--- Return the cleaned struct. --->
  • <cfreturn ARGUMENTS.Struct />
  •  
  • <cfelse>
  • <cfreturn />
  • </cfif>
  • </cffunction>

Here, this ColdFusion user defined function is looking for the following keys:

  • CreditCard
  • CCNumber
  • CCNum
  • ExpirationDate
  • Expry
  • ExpDate
  • CCExp

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 »

  • <!--- Kill extra output. --->
  • <cfsilent>
  •  
  • <!--- Set some sercur form data. --->
  • <cfset FORM.CreditCard = "1234567890123456" />
  • <cfset FORM.ExpirationDate = "12/12" />
  •  
  • </cfsilent>
  •  
  • <html>
  • <head>
  • <title>ColdFusion CFError Handling Tutorial</title>
  • </head>
  • <body>
  •  
  • <!--- Force an error. --->
  • <cfset asdf = sf />
  •  
  • </body>
  • </html>

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:


 
 
 

 
 ColdFusion CFError Email CFDump Data Display  
 
 
 

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

Post Comment  |  Ask Ben  |  Other Searches  |  Print Page




Learning ColdFusion 9 - ColdFusion 9 tutorials, samples, examples, demos

Reader Comments

Aug 29, 2007 at 10:10 AM // reply »
54 Comments

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


Aug 29, 2007 at 10:22 AM // reply »
7,572 Comments

@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 :)


Aug 29, 2007 at 10:28 AM // reply »
26 Comments

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.


Aug 29, 2007 at 11:23 AM // reply »
31 Comments

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.


Aug 29, 2007 at 12:06 PM // reply »
17 Comments

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?


Aug 29, 2007 at 1:02 PM // reply »
42 Comments

@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>


Aug 29, 2007 at 3:50 PM // reply »
31 Comments

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.


Sep 9, 2007 at 6:07 PM // reply »
7,572 Comments

@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.


Sep 9, 2007 at 10:08 PM // reply »
31 Comments

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.


Jul 14, 2009 at 2:45 PM // reply »
2 Comments

Ben,
I have been looking to use something like this for a while. I seem to be having an issue receiving emails from the script. I have experienced this before on another script a while back while I didn't really need the feature but now I really need the email delivery feature. Any suggestions.


Jul 18, 2009 at 2:06 PM // reply »
7,572 Comments

@Chris,

Are you checking your Mail logs in the CF Admin?


Don
Dec 10, 2009 at 9:18 AM // reply »
2 Comments

Re: Chris's post of 7/14/09: I am on a shared CF8 server, and have been using an error trap script for some while on other sites. But on this new CF8 site the cferror template never sends email. After exhaustive testing I have confirmed that all CFM tags are fine, just no mail ever goes out.

I have had to modify the error script to append error information to a text file, which I will periodically look at, but this is a kludge.

2 questions: Is CF8 <cferror> known to have this trouble? If not, what might the Mail log report that I can refer the hosting company to?

Thanks!


Don
Dec 10, 2009 at 9:25 AM // reply »
2 Comments

Ben,

Well, after trying out the <cferror> template that you show in the article above, I actually received an error email from my site! So, either the host found and fixed something and did not notify me, or there's something in my tried-and-true error template which does not work on CF8.

But I am much more satisfied with your approach, so I will be implementing it across my sites.

Thanks!! Problem resolved.


Dec 13, 2009 at 5:34 PM // reply »
7,572 Comments

@Don,

Hmm, no idea why mine would work and the prior one wouldn't; but, glad you got it working ;)


Jan 18, 2010 at 8:41 AM // reply »
4 Comments

Can we use CFOUTPUT in the template which we have specified in the cferror tag.

Because when I try to use it, I don't see any effect of cfoutput.

eg. <cfoutput>#test#</cfoutput>

- it gives me the output as #test#


Jan 18, 2010 at 8:47 AM // reply »
7,572 Comments

@Anwar,

You should be able to use CFOutput as far as I know. If you can use CFMail (which I am in this blog post), you should be able to use CFMail since CFMail is basically doing an implicit CFOutput; I would think it odd that it would work in one way and not in another.

Make sure you have the right TYPE set on your CFError tag; I think one of the type options doesn't allow CF tags to execute... but it's been a long time since I've used this. If you are using CF7+, I would suggest looking into Application.cfc with the OnError() event handler.


Jan 18, 2010 at 9:30 AM // reply »
4 Comments

@Ben Nadel,

I am using CF 7 MX.

And Application.cfm for error handling with cferror Type=request.

As you said - there might be the problem with the "request" type.

But, if I change type to "exception" then I am not able to catch the errors such as

--- Element xyz is undefined in the FORM.

So, is there any way I can catch these type of errors and still able to use CFOUTPUT.

Thanks,

Anwar


Jan 18, 2010 at 1:47 PM // reply »
7,572 Comments

@Anwar,

I believe that "Request" error types do NOT let you run CFML in the error handler (or at least, very very limited CFML).

I am surprised that the "Exception" type doesn't let you catch those "undefined" errors. It really should from what I remember.


Jan 21, 2010 at 5:25 AM // reply »
4 Comments

@Ben Nadel,

Got it now, there was error in my Error Handler template - and the CF server was not showing the error which occured in Error handler template - but instead it was showing the Original error. So it thought that the errors are handled in case of "Exception" type.

Thanks.


Jan 21, 2010 at 5:25 AM // reply »
4 Comments

@Ben Nadel,

Got it now, there was error in my Error Handler template - and the CF server was not showing the error which occurred in Error handler template - but instead it was showing the Original error. So it thought that the errors are handled in case of "Exception" type.

Thanks.


Jan 21, 2010 at 9:22 PM // reply »
7,572 Comments

@Anwar,

Ah gotcha - yeah, that will get you ever time. If your error handlers break (same for onError() event handler), they just start throwing errors. I assume this is meant to prevent an infinite loop of error handling :)


Post Comment  |  Ask Ben

Recent Blog Comments
Mar 21, 2010 at 6:32 AM
ColdFusion CFPOP - My First Look
Apologies... The field name in the db for C. is "BounceCode" It stores the code / message which is returned in the email. Sorry for the confusion. ... read »
Mar 21, 2010 at 6:29 AM
ColdFusion CFPOP - My First Look
@Jose Galdamez, Hi Ben and Jose 1st of all.. big thanks to Jose for his Skype chat a few weeks back. Your time was much appreciated. I have come up with a rather unelegant solution to my problem a ... read »
Mar 21, 2010 at 3:42 AM
A New Wrist Pain
Chiropractic treatment is one of the best methods for treating numerous health problems naturally. After years of experience being a chiropractor, I have found that it is a powerful way to solve many ... read »
Mar 20, 2010 at 12:07 PM
Drawing On The iPhone Canvas With jQuery And ColdFusion
Simply awesome. Saved my day. ... read »
Mar 20, 2010 at 9:00 AM
Building A Fixed-Position Bottom Menu Bar (ala FaceBook)
I would like to say thx for an easy way to create a bottom bar. I do have a ?. Is it possible to center the bar if i want to resize it to ex 85%. Regards Offenbach ... read »
Mar 19, 2010 at 7:26 PM
MySQL 3/4 - com.mysql.jdbc.Driver And allowMultiQueries=true
Thank you very much for this post. Adding allowMultiQueries="true" in context.xml didn't help until I added it to url as allowMultiQueries=true Good idea is to use prepared statements and it will he ... read »
Jim
Mar 19, 2010 at 4:49 PM
Nobody Puts Baby In The Corner!
Wow. This is like suddenly finding a support group for your secret shame. I'm not alone! I always liked this movie, even though it is extremely cheesy. I just wish Jennifer Grey hadn't gotten the ... read »
Mar 19, 2010 at 4:47 PM
Application.cfc OnRequest() Method Affects OnError() Arguments
@Jason and @Ben, I've been doing some CF9 refactoring on our systems and noticed an odd occurrence with onError as well. Found a way to work around my problem, but what I saw was... Background: Our ... read »