facebook google plus twitter
Webucator's Free ColdFusion MX Tutorial

Lesson: Handling Errors

Welcome to our free ColdFusion MX tutorial. This tutorial is based on Webucator's Comprehensive ColdFusion Training course.

Lesson Goals

  • To debug and fix compiler errors
  • To create custom error pages
  • To use structured error handling with cftry/cfcatch blocks
  • To work with <cflock> to handle race conditions

Handling Exceptions

There are two types of exceptions in ColdFusion: compiler exceptions and runtime exceptions.

Compiler Exceptions

Compiler exceptions occur when a ColdFusion page cannot run at all because of some error in the code. For example, a compiler exception could be caused by any of the following:

  • missing angle bracket
  • an extraneous pound sign (#)
  • a missing required attribute
  • an attribute that doesn't belong

The following page will not compile:

Code Sample:

Errors/Demos/CompilerException.cfm
<cfif></cfif>

Part of the resulting error page is shown below:

The suggestions are generally not very helpful; however, in most cases the cause of a compiler exception is not too difficult to identify. Sometimes on pages with a lot of code, it can be difficult to locate the problem, especially if it is caused by a missing or extra close tag in code with a lot of nested tags. To illustrate, take a look at the following page.

Code Sample:

Errors/Demos/CompilerException2.cfm
<cfset a = "dog">
<cfset b = "cat">
<cfset c = "mouse">
<cfset d = "horse">
<cfset e = "cow">
<cfset f = "pig">
<cfset g = "donkey">

<cfif true>
<cfoutput>#a#</cfoutput>
<cfif true>
<cfif true>
<cfoutput>#b#</cfoutput>
</cfif>
<cfoutput>#c#</cfoutput>
<cfif true>
<cfif true>
<cfoutput>#d#</cfoutput>
</cfif>
<cfif true>
<cfoutput>#e#</cfoutput>
</cfif>
</cfif>
<cfoutput>#f#</cfoutput>
</cfif>
</cfif>
<cfoutput>#g#</cfoutput>
</cfif>

For simplicity, we have used "true" where a condition would be, but imagine that these conditions might be true or false under different circumstances.

Part of the resulting error page is shown below:

So, you're missing a start <cfif>. How do you identify which one? Or perhaps the real problem is that you have one too many close </cfif> tags.

One problem with this type of error is that you can get rid of the exception, but leave yourself with bad logic. For example, you could simply remove the last </cfif>. The exception goes away and the page displays: "dog cat mouse horse cow pig donkey".

But, suppose that you want the output to read: "dog cat mouse pig donkey". This kind of problem can be tricky to solve and unfortunately there is no simple answer, but we have a few recommendations that should make your coding life easier.

  1. Indent your code. This seems like a no brainer, but we see a lot of code written by experienced developers that is impossible to read simply because it isn't laid out nicely. Dreamweaver has a great "Apply Source Formatting" feature (under the Commands menu) to help lay out the code, but we recommend indenting as you write as it will help you avoid these types of errors in the first place.
  2. Nest your code sensibly. For example, don't use multiple <cfoutput> tags when one will do the trick.
  3. Use comments. Everybody recommends them, but they are used too infrequently. Comments will help you identify problems in your code and will allow you to focus on the solution. Even small comments can be helpful.

We have made some changes to our animals page:

Code Sample:

Errors/Demos/CompilerException2-formatted.cfm
<cfset a = "dog">
<cfset b = "cat">
<cfset c = "mouse">
<cfset d = "horse">
<cfset e = "cow">
<cfset f = "pig">
<cfset g = "donkey">

<cfoutput>
<cfif true>
	#a# <!---dog--->
	<cfif true>
		<cfif true>
			#b#<!---cat--->
			<cfif true>
				#c#<!---mouse--->
					<cfif true>
						#d#<!---horse--->
					</cfif>
					<cfif true>
						#e#<!---cow--->
					</cfif>
				</cfif>
				#f#<!---pig--->
			</cfif>
		</cfif>
		#g#<!---donkey--->
	</cfif>
</cfif>
</cfoutput>

The error hasn't been fixed, but now it is a lot easier to identify the problem. We know that we want the output to read "dog cat mouse pig donkey". We do not want "horse" and "cow" to be output. There are at least a couple of ways we can solve this. One is shown below:

Code Sample:

Errors/Demos/CompilerException2-fixed.cfm
<cfset a = "dog">
<cfset b = "cat">
<cfset c = "mouse">
<cfset d = "horse">
<cfset e = "cow">
<cfset f = "pig">
<cfset g = "donkey">

<cfoutput>
<cfif true>
	#a# <!---dog--->
	<cfif true>
		<cfif true>
			#b#<!---cat--->
			<cfif true>
				#c#<!---mouse--->
				<cfif false>
					<cfif true>
						#d#<!---horse--->
					</cfif>
					<cfif true>
						#e#<!---cow--->
					</cfif>
				</cfif>
				#f#<!---pig--->
			</cfif>
		</cfif>
		#g#<!---donkey--->
	</cfif>
</cfif>
</cfoutput>

Runtime Exceptions

Runtime exceptions are errors that are caught at the time the code is executed. They can be handled at the site level, at the application level, and at the page level. In this section, we will look at handling runtime exceptions at the site and application levels. In the next section we will look at page-level structured error handling.

Missing Template Handler Page

In ColdFusion Administrator, under Server Settings -> Settings, you can specify a missing template handler. Usually, you'll use this to provide a more friendly error. You may want to include your navigation bars on the template to make it easier for the users to find what they are looking for.

Site-wide Error Handler Page

In ColdFusion Administrator, under Server Settings > Settings, you can specify a site-wide error handler. This page can contain CFML, but you should nest anything potentially dangerous in try/catch blocks, which we will discuss shortly. The following example logs the error, returns an error to the screen (probably with too much information), and emails the site administrator an error dump. Note that the site-wide error handler will be used for compiler exceptions as well.

Code Sample:

Errors/Demos/SiteWideErrorPage.cfm
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Oops!</title>
<style type="text/css">
	.Label {margin-top:10px; font-weight:bold; width:200px;}
	.Details {margin-left:10px; font-style:italic; width:500px;}
</style>
</head>
<body>
<h1>Oops!</h1>
<p>Sorry, we have experienced and error.</p>
<cftry>
	<!---LOG ERROR--->
	<cflog
		text="#ERROR.Diagnostics#, #ERROR.HTTPReferer#, #ERROR.Template##ERROR.QueryString#"
		log="application" type="Error">   
	<!---OUTPUT ERROR INFORMATION - may not want to do this on a live application--->
	<cfoutput>
		<div class="Label">Time of Error:</div>
		<div class="Details">#DateFormat(ERROR.DateTime)# at #TimeFormat(ERROR.DateTime)#</div>
		
		<div class="Label">Diagnostic Information:</div>
		<div class="Details">#ERROR.Diagnostics#</div>
		
		<div class="Label">Referrer:</div>
		<div class="Details">#ERROR.HTTPReferer#</div>
		
		<div class="Label">Template Causing Error:</div>
		<div class="Details">#ERROR.Template#?#ERROR.QueryString#</div>
	</cfoutput>
<cfcatch>
	No information available.
</cfcatch>
</cftry>

<!---SEND EMAIL TO SITE ADMINISTRATOR--->
<cftry>
	<cfmail to="admin@webucator.com" subject="#ERROR.Template# error" type="html" from="errors@webucator.com">
		<cfdump var="#ERROR#">
	</cfmail>
	<hr/>
	<p>The site administrator has been informed.</p>
	<cfcatch type="any">
		<cftry>
			<hr/>
			<cfoutput>
				<p>Please email the site administrator at 
				<a href="mailto:admin@webucator.com">admin@webucator.com</a>.</p>
			</cfoutput>
			<cfcatch type="any"></cfcatch>
		</cftry>
	</cfcatch>
</cftry>
</body>
</html>

Run Errors/Demos/SiteWideErrorDemo.cfm, which tries to read an undefined variable, in your browser and you will get the following result:

The logged error (in cfusion/cfusion-ear/cfusion-war/WEB-INF/cfusion/logs/application.log) will read something like:

"Error","jrpp-14","08/12/06","15:26:51","WEBUCATORCFM","Element FOO is undefined in VARIABLES. <br>The error occurred on line 6., , /Courseware/CFM201/ClassFiles/Errors/Demos/SiteWideErrorDemo.cfm"

Handling Errors with <cferror>

The <cferror> tag, which generally goes in the Application.cfm file, is used to indicate custom error templates for different types of errors. The type is specified with <cferror>'s type attribute. Possible values are:

  1. validation - has to do with server-side form validation, which we are not covering here.
  2. exception - displayed when runtime exceptions occur. You can use different templates for different types of exceptions.
  3. request - serves as a backup if errors occur in exception error templates. You cannot use CFML tags in this template, but you may output error variables by enclosing them in pound signs (e.g, #ERROR.Template#).

The following example shows a simple Application.cfm file that makes use of <cferror>. It is followed by examples of an exception error template and a request error template.

Code Sample:

Errors/Demos2/Application.cfm
<cfapplication sessionmanagement="yes" clientmanagement="yes" name="ErrorDemos">

<cferror type="exception" template="ErrorException.cfm" mailto="admin@webucator.com">
<cferror type="request" template="ErrorRequest.cfm" mailto="admin@webucator.com">
<cferror type="validation" template="ErrorValidation.cfm" mailto="admin@webucator.com">

Code Sample:

Errors/Demos2/ErrorException.cfm
<html>
<head>
	<title>Exception</title>
</head>

<body>
<h1>Exception Error!</h1>
<cfoutput>
<table border="1">
	<tr>
		<th>Name</th>
		<th>Value</th>
	</tr>
	<tr>
		<td>Error.Browser</td>
		<td>#Error.Browser#</td>
	</tr>
	<tr>
		<td>Error.DateTime</td>
		<td>#Error.DateTime#</td>
	</tr>
	<tr>
		<td>Error.Diagnostics</td>
		<td>#Error.Diagnostics#</td>
	</tr>
	<tr>
		<td>Error.GeneratedContent</td>
		<td>#Error.GeneratedContent#</td>
	</tr>
	<tr>
		<td>Error.HTTPReferer</td>
		<td>#Error.HTTPReferer#</td>
	</tr>
	<tr>
		<td>Error.MailTo</td>
		<td>#Error.MailTo#</td>
	</tr>
	<tr>
		<td>Error.QueryString</td>
		<td>#Error.QueryString#</td>
	</tr>
	<tr>
		<td>Error.Template</td>
		<td>#Error.Template#</td>
	</tr>
	<tr>
		<td>Error.RemoteAddress</td>
		<td>#Error.RemoteAddress#</td>
	</tr>
	<tr>
		<td>Error.Type</td>
		<td>#Error.Type#</td>
	</tr>
	<tr>
		<td>Error.RootCause</td>
		<td>#Error.RootCause#</td>
	</tr>
	<tr>
		<td>Error.Message</td>
		<td>#Error.Message#</td>
	</tr>
</table>
</cfoutput>
</body>
</html>

Code Sample:

Errors/Demos2/ErrorRequest.cfm
<html>
<head>
	<title>Request Error</title>
</head>

<body>
<h1>Request Error!</h1>

<table border="1">
	<tr>
		<th>Name</th>
		<th>Value</th>
	</tr>
	<tr>
		<td>Error.Browser</td>
		<td>#Error.Browser#</td>
	</tr>
	<tr>
		<td>Error.DateTime</td>
		<td>#Error.DateTime#</td>
	</tr>
	<tr>
		<td>Error.Diagnostics</td>
		<td>#Error.Diagnostics#</td>
	</tr>
	<tr>
		<td>Error.GeneratedContent</td>
		<td>#Error.GeneratedContent#</td>
	</tr>
	<tr>
		<td>Error.HTTPReferer</td>
		<td>#Error.HTTPReferer#</td>
	</tr>
	<tr>
		<td>Error.MailTo</td>
		<td>#Error.MailTo#</td>
	</tr>
	<tr>
		<td>Error.QueryString</td>
		<td>#Error.QueryString#</td>
	</tr>
	<tr>
		<td>Error.Template</td>
		<td>#Error.Template#</td>
	</tr>
	<tr>
		<td>Error.RemoteAddress</td>
		<td>#Error.RemoteAddress#</td>
	</tr>
</table>

</body>
</html>

To generate an exception error, open any of the following files:

Code Sample:

Errors/Demos2/CauseException1.cfm
<cfinclude template="Missing.cfm">

Code Sample:

Errors/Demos2/CauseException2.cfm
<cf_foo>

Code Sample:

Errors/Demos2/CauseException3.cfm
<cfquery datasource="NoSuchDS"></cfquery>

To generate a request error, run any of the files above after doing one of the following:

  1. Comment out the exception error template in Errors/Demos2/Application.cfm.
  2. Make the ColdFusion code in Errors/Demos2/ErrorException.cfm invalid.

Error Variables

Exception and request errors share the following variables:

Exception and Request Error Variables
Variable Description
ERROR.browser The user's browser.
ERROR.dateTime The date and time of the error.
ERROR.diagnostics Detailed diagnostic information.
ERROR.generatedContent Any content generated by the request before the error occurred.
ERROR.HTTPReferer The referring page, if any.
ERROR.mailTo The email address of the administrator as set in the <cferror> tag.
ERROR.queryString Query string of the request, if any.
ERROR.remoteAddress The IP address if the user's machine.
ERROR.template The page being executed when the error occurred.

In addition, exception errors have these additional variables:

Exception-only Error Variables
Variable Description
ERROR.message Message associated with the exception.
ERROR.rootCause A structure containing the data returned by <cfcatch>.
ERROR.tagContext An array of structures containing information for each tag that is currently open.
ERROR.type The exception type.

Handling Errors with the onError Method

The onError method of the Application.cfc component works much like the <cferror> tag. The method gets two arguments:

  1. An exception structure.
  2. The name of the Application.cfc method in which the error occurred. This will be blank unless the error occurred in some of the code in the Application.cfc file.

The method is generally used to log errors and to display a friendly error message. It can also be used to email information about the error to the site administrator. A sample is shown below.

Code Sample:

Errors/Demos3/Application.cfc
<cfcomponent>
<cfset THIS.name = "ErrorDemos">
<cffunction name="onRequestStart">	
	<!---Uncomment the tag below to cause an exception in this file--->
	<!---<cf_foo>--->
</cffunction>

<cffunction name="onError" output="true">
	<cfargument name="Except" required="true"/>
	<cfargument name="EventName" type="String" required="true"/>
	
	<!---Log Errors--->
	<cfset Message = Except.message>
	<cfif Len(EventName)>
		<cfset Message = Message & " The error occurred in " & EventName & ".">
	<cfelse>
		<cfset Message = Message & " The error occurred in " & CGI.SCRIPT_NAME & ".">
	</cfif>
	<cflog file="#THIS.Name#" type="error" text="Message: #Message#">
	
	<!--- Throw validation errors to ColdFusion for handling. --->
	<cfif Find("coldfusion.filter.FormValidationException", ARGUMENTS.Except.StackTrace)>
		<cfthrow object="#except#">
	<cfelse>
		<!--- You can replace this cfoutput tag a friendlier error message. --->
		<cfoutput>
			<h1>Error Event: #EventName#</h1>
			<h2>Error details:</h2>
			<cfdump var="#except#">
		</cfoutput>
	</cfif>
</cffunction>
</cfcomponent>

If an uncaught exception occurs, the onError method is invoked. It does the following:

  1. Logs the error. If the exception occurred in an Application.cfc method, it reports the method name. Otherwise, it reports the path to the requested page. It uses the application name (THIS.name) as the name of the log file, so that it can be found easily.
  2. Determines if the error is a form validation exception, in which case, it turns the error over to ColdFusion to handle. If it is not, then it outputs an error message.

Fixing Compiler Exceptions

Duration: 10 to 15 minutes.

In this exercise, you will fix a compiler exception similar to the one we just saw. Your goal is to get the output to read "You have fixed the error!"

  1. Open Errors/Exercises/CompilerException.cfm in your editor and save it as CompilerException2.cfm.
  2. Fix it. If you get messed up, you can recopy the original Errors/Exercises/CompilerException.cfm.
  3. To test your solution, open Errors/Exercises/CompilerException2.cfm in your browser.

Solution:

Errors/Solutions/CompilerException.cfm
<cfif true>
	You <!---Yes--->
	<cfif true>
		<cfif true>
			have <!---Yes--->
			<cfif true>
				fixed <!---Yes--->
				<cfif false>
					<cfif true>
						some <!---No--->
					</cfif>
					<cfif true>
						of <!---No--->
					</cfif>
				</cfif>
				the <!---Yes--->
			</cfif>
		</cfif>
		error! <!---Yes--->
	</cfif>
</cfif>

cftry/cfcatch

So far, we have been looking at how to handle uncaught exceptions. It's much better if you can anticipate the exception, catch it, and respond. We do this with try/catch blocks. The syntax is shown below:

<cftry> <!---Code that might cause a problem---> <cfcatch type="Exception_Type"> <!---Error message---> </cfcatch> </cftry>

The major benefit of using try/catch blocks is that you can continue processing the page in the event that an error occurs. For example, you might have code that inserts a record into a database. Such code can fail if the database server is down (or for a number of other reasons). If the insert fails, you could catch the error and report it by email to the site administrator. However, the email code could also fail, so you might put that in a try/catch block as well. The code might look like this:

Code Sample:

Errors/Demos/TakeTwo.cfm
               <cftry>
	<cfquery datasource="dsn">
		--Insert record
	</cfquery>
	Record inserted.
	<cfcatch type="database">
		<cftry>
			<cfmail to="admin@webucator.com" from="errors@webucator.com" subject="Database error" type="html">
				Database error.  Record could not be inserted.
				<cfdump var="#cfcatch#">
			</cfmail>
			Sorry, we could not insert the record.  The site administrator has been informed.
			<cfcatch type="any">
				Sorry, we could not insert the record.  Please email the site administrator at admin@webucator.com.
			</cfcatch>
		</cftry>
	</cfcatch>
</cftry>
            

Exception Types

The table below shows the types of exceptions that can occur. These values can be used for the type attribute of the <cfcatch> tag.

Exception Types
Exception Type Description
Any All exceptions. Useful if you're not sure what type of exception could occur.
Application Custom exceptions thrown by <cfthrow> that either do not specify the type of exception or specify "Application" as the type.
Expression Invalid expressions, such as trying to concatenate with a plus sign (+).
Lock Occur when a cflock times out.
MissingInclude Errors where files specified by the <cfinclude>, <cfmodule>, and <cferror> tags cannot be found.
Object Errors that occur in code dealing with objects.
SearchEngine Errors that occur in code dealing with the Verity search engine.
Security Errors that occur in security code.
Template Errors that occur in ColdFusion syntax. These errors are usually caught at compile time.

Locking Code to Prevent Errors

ColdFusion is a multi-threaded application, which means that it can process many requests at the same time. In some case, two requests may be trying to access the same memory space or the same file. Any code that lends itself to this possibility should be locked. To understand why, you need to understand the concept of a race condition.

Race Conditions

Race conditions occur when multiple requests attempt to modify the same data at the same moment. In Macromedia ColdFusion MX 7 Web Application Construction Kit, the authors use the following example:

<cfset APPLICATION.hitCount = APPLICATION.hitCount + 1>

In this one line of code, APPLICATION.hitCount is being read and modified. Although this happens very quickly, it is possible that two simultaneous requests read the value at the exact same time before either of them has the chance to modify it. Hence, they each read the same value (e.g, 100). They both then add 1 to the value and come up with the same number and set the new hitCount to 101. But since there were actually two separate hits, the new hitCount should be 102.

Although hit counts are not usually mission critical, this does illustrate the problem. The solution is to wrap the code in an exclusive lock:

<cflock type="Exclusive" scope="application" timeout="10"> <cfset APPLICATION.hitCount = APPLICATION.hitCount + 1> </cflock>

This forces single-threaded access to this code, which will prevent a race condition from occurring. We used an Exclusive lock, because the memory variable is being modified. In cases where the variable is only being read, you could use a ReadOnly lock:

<cflock type="ReadOnly" scope="application" timeout="10"> <cfoutput>Page hits: #APPLICATION.hitCount#</cfoutput> </cflock>

Named Locks

The examples above used scoped locks, which lock the whole application scope until the code inside the lock has been processed. While this is certainly safe, it can be overkill. To see how, take a look at the following code:

//In the onRequestStart method of Application.cfc <cflock type="Exclusive" scope="application" timeout="10"> <cfset APPLICATION.hitCount = APPLICATION.hitCount + 1> </cflock> //In the onSessionStart method of Application.cfc <cflock type="Exclusive" scope="application" timeout="10"> <cfset APPLICATION.sessionCount = APPLICATION.sessionCount + 1> </cflock>

While it's possible that these two pieces of code might be accessed at the exact same time by multiple users, this won't cause any conflict. Yet the way it is written, each request will have to cue up behind the next. Using named locks provides more granularity:

//In the onRequestStart method of Application.cfc <cflock type="Exclusive" name="HitCountLock" timeout="10"> <cfset APPLICATION.hitCount = APPLICATION.hitCount + 1> </cflock> //In the onSessionStart method of Application.cfc <cflock type="Exclusive" name="SessionCountLock" timeout="10"> <cfset APPLICATION.sessionCount = APPLICATION.sessionCount + 1> </cflock>

This way, requests only get cued up behind locks of the same name. A couple of warnings:

  • Be very careful to spell your lock names the same! Typos are very dangerous.
  • Lock names are server-wide, so if you are running multiple applications on the same server, you will want to include the application's name in the lock name (e.g, name="#APPLICATION.applicationName#SessionCountLock").