Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at RIA Unleashed (Nov. 2009) with:

Attaching ColdFusion Error Reports To Avoid GMail Spam

By Ben Nadel on
Tags: ColdFusion

I am sure that all of us keep track of the errors generated by our ColdFusion web sites. I personally like to email myself the exception data using Application.cfc's OnError() event handler. Traditionally, what I do is just CFDump out a bunch of the relevant scopes (Exception, CGI, FORM, URL) in the email body and send it off. But recently, GMail (my email provider) seems to have cracked down on their spam filtering and these error reports, unbeknownst to me, have been going directly to my spam folder. Apparently, GMail doesn't much like an email that has over 50K worth of data directly in its body.

To fix this problem, all I had to do was convert the error report from the body content into an attachment. With ColdFusion 8 and the CFMailParam Content attribute, this could not have been easier. Here is an abridged version of my Application.cfc with the OnError() event handler defined:

Application.cfc

  • <cfcomponent
  • output="false"
  • hint="I define the application settings and event handlers.">
  •  
  • <!--- Define the application. --->
  • <cfset this.name = hash( getCurrentTemplatePath() ) />
  • <cfset this.applicationTimeout = createTimeSpan( 0, 0, 5, 0 ) />
  •  
  •  
  • <cffunction
  • name="onError"
  • access="public"
  • returntype="void"
  • output="true"
  • hint="I handle any uncaught errors within the application.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="exception"
  • type="any"
  • required="true"
  • hint="I am the uncaught exception object."
  • />
  •  
  • <cfargument
  • name="event"
  • type="string"
  • required="false"
  • default=""
  • hint="I am the event handler in which the error occurred."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!---
  • First things first, let's set the page timeout high
  • in case we are recoverring from a timeout issue on
  • the page. This will give us time to recover.
  • --->
  • <cfsetting requesttimeout="#(5 * 60)#" />
  •  
  •  
  • <!---
  • Build the collection of scopes we want to output
  • in our debuggin report.
  • --->
  • <cfset local.scopes = [
  • {
  • name = "Exception",
  • data = arguments.exception
  • },
  • {
  • name = "Form",
  • data = form
  • },
  • {
  • name = "Url",
  • data = url
  • },
  • {
  • name = "CGI",
  • data = cgi
  • }
  • ] />
  •  
  • <!---
  • Now that we have time to operate our error recovery,
  • let's build a report of th error.
  • --->
  • <cfsavecontent variable="local.report">
  •  
  • <h1>
  • An Error Occurred
  • </h1>
  •  
  • <!---
  • Loop over the given scopes to output each one
  • for debugging purposes.
  • --->
  • <cfloop
  • index="local.scope"
  • array="#local.scopes#">
  •  
  • <h2>
  • #UCase( local.scope.name )# Data
  • </h2>
  •  
  • <cfdump
  • var="#local.scope.data#"
  • label="#local.scope.name# Data"
  • top="5"
  • />
  •  
  • </cfloop>
  •  
  • </cfsavecontent>
  •  
  •  
  • <!---
  • Mail out the report. Attach the primary report to
  • the email so that GMail does not block it as spam.
  • Put only minial debug information in the mail body.
  • --->
  • <cfmail
  • to="ben@xyz.com"
  • from="errors@xyz.com"
  • subject="ERROR: BenNadel.com : #arguments.exception.message#"
  • type="html">
  •  
  • <h1>
  • An Error Occurred on BenNadel.com
  • </h1>
  •  
  • <h2>
  • Message
  • </h2>
  •  
  • <p>
  • <cfif len( arguments.exception.message )>
  • #arguments.exception.message#
  • <cfelse>
  • <em>No message</em>
  • </cfif>
  • </p>
  •  
  • <h2>
  • Details
  • </h2>
  •  
  • <p>
  • <cfif len( arguments.exception.detail )>
  • #arguments.exception.detail#
  • <cfelse>
  • <em>No details</em>
  • </cfif>
  • </p>
  •  
  • <!--- Check to see if there is a tag context. --->
  • <cfif arrayLen( arguments.exception.tagContext )>
  •  
  • <!---
  • Get a shorthand reference to the top tag
  • context for easy output.
  • --->
  • <cfset local.context = arguments.exception.tagContext[ 1 ] />
  •  
  • <h2>
  • Tag Context
  • </h2>
  •  
  • <dl>
  • <cfloop
  • item="local.contextKey"
  • collection="#local.context#">
  •  
  • <dt>
  • #local.contextKey#
  • </dt>
  • <dd>
  • #local.context[ local.contextKey ]#
  • </dd>
  •  
  • </cfloop>
  • </dl>
  •  
  • </cfif>
  •  
  •  
  • <!---
  • Attach our full debugging report to the email so
  • that GMail does not block the raw data as SPAM.
  • --->
  • <cfmailparam
  • content="#local.report#"
  • file="error_report.htm"
  • />
  •  
  • </cfmail>
  •  
  •  
  • <!---
  • Output something to the page. Try to redirect to
  • the error page. If that fails (because the response
  • has already been committed), then try a javascript
  • redirect.
  • --->
  • <cftry>
  •  
  • <!--- Redirect to error page. --->
  • <cflocation
  • url="./error.cfm"
  • addtoken="false"
  • />
  •  
  • <!--- Catch relocation error. --->
  • <cfcatch>
  •  
  • <!--- Use javascript. --->
  • <script type="text/javascript">
  • location.href= = "./error.cfm";
  • </script>
  •  
  • </cfcatch>
  • </cftry>
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>

Notice that rather than CFDumping out all of the scopes directly into the email body, I'm storing them in a content buffer. Then, using the CFMailParam tag, I'm attaching that content buffer to the email as the file, "error_report.htm." The Content attribute allows us to create a file attachment without having to write to the file system. This non-file, file approach lets us leverage the benefits of mail spooling without having to worry about file cleanup.

In addition to the attached error report, I also like to put a minimal amount of basic debug information directly in the email. Often times, the error message itself and the primary error context are enough for me to fix the problem. The attached error report then acts as a data reference if I don't know enough.

To test this, I set up the following code that purposefully references an undefined scope key:

  • <!--- Reference undefined variable. --->
  • <cfset girl = yourPants.Tricia />

This error resulted in the following email:

 
 
 
 
 
 
CFMailParam Can Be Used To Attach Content To An Email To Help Avoid Spam Filtering. 
 
 
 

So far, this approach has been able to avoid GMail's strengthened spam filtering. I guess, as a general rule, avoid sending massive email bodies.




Reader Comments

Excellent idea Ben - thanks for that.

I have got used to having to check my spam folder for error reports rather than addressing the issue.

This is a very elegant way of solving it.

I'm going to bear this in mind for other report e-mails etc.. Nice.

Reply to this Comment

@Ben:

Why not just create a filter to prevent Gmail from sending the folder to spam. You should be able to set up a filter to prevent e-mail from going to spam.

You could do this e-mail by the from address or by checking the subject line and then just choose the "Never send it to Spam" option on the action portion of the filter. That should prevent messages from going to spam.

Reply to this Comment

@Dan,

Oh snap, I have filters set up, but I never saw the "never send to spam" checkbox at the bottom. Hot damn!

That said, I guess you can take the blog post as a suggest to never CFDump things into an email anyway??? I think the mail attachment feels like a better way to go, especially for a large amount of information.

Reply to this Comment

@Ben,

Yeah I feel that as well. When I read this I thought of the rules method but actually feel this is a nicer way to handle complex e-mails. Makes it nice and easy to save off the attachment as well if you need to store it etc without Outlook or other mail clients butchering the code.

Reply to this Comment

@James,

Word up. Plus, the output of CFDump in GMail is generally lacking readability and will slow down the page as I have noticed.

Reply to this Comment

@Ben

Aha, yeah that's another good point! It always bugged me when I wasn't able to properly see the CFDump output from within the web client.

On the fly CFMail attachments FTW. ;)

Reply to this Comment

Ben, thanks. I had the same problem with my work email but I just pulled the HTML out and formatted the email as text. Not as pretty but it works.

Question for you: Will onError() override the defaultExceptionHandler in the ColdSpring.xml of Model-Glue Unity?

Reply to this Comment

@Jen,

I am not sure about how errors are handles in Frameworks (my experience is minimal). I might make a bold statement like it would be CRAZY if a framework crippled the inherent behavior of the ColdFusion Application Framework... but I might get laughed out of the room.

Reply to this Comment

@Jen,

I'm not 100% but by default MG:U doesn't use application.cfc but I may have read it can be made to use it.. It's something that crossed my mind a while ago.

You can still use the default exception handler to do the same thing as Ben is doing here though. Just broadcast for a handler which takes care of the error processing and e-mail..

Reply to this Comment

I just pointed out the filter method, because it's the more "full proof" method.

While this change may reduce the chance of your e-mails going into the spam folder, there's always the chance that Gmail might still flag the message as spam.

The filter method should prevent that completely. So I'd still set up a filter to ensure your error reports are getting through to you.

Reply to this Comment

Oh yeah, one other thing to remember about CFDUMP (and as Jen may have hinted at) is that it supports outputting as plain text. This can be useful if you're sending to a device/client that doesn't handle complex HTML well.

It's not nearly as pretty, but its really lightweight.

Reply to this Comment

@Ben, @James,

Thanks. It appears that the MG:U exception handler overrides the onError() except when there is an error in the framework itself (i.e., invalid XML in MG.xml or error in a controller like vars not being declared at top of function).

So using both is kind of a fail safe (or safe fail in this case).

Reply to this Comment

Couldn't you also simply write this html file to a secure directory on your server? I suppose storing it gmail saves server space. However storing them in your server would allow you to do things like, create a new folder for a new set of errors when they occur. Also you could use a simple verity collection or even easier, cfdirectories to tally your errors and when they occur. Just my 2 cents.

-Toby

Reply to this Comment

@Toby,

You could certainly store them on the server; my only concern there is that they are, by nature, transient. Meaning, once the error occurs and is fixed, that file no longer has any value. Having on the server, I would think, adds the overhead of cleanup. Emailing it, on the other hand, is automatically removed from the server.

But, I suppose, if you wanted to keep a library of the errors that you could search through, which is kind of a cool idea especially for hard-to-debug problems over time, then that would make some good sense.

Reply to this Comment

Trying this out. I didn't have a problem with gmail and spam cause I used the "never send to spam", but I hated the fact that all the styling was not shown correctly.

Reply to this Comment

I just can't help but think the cfdump might be wht tricias not in yourpants. ;^)

Reply to this Comment

I do the same thing, but the biggest issue I have, is web bots crawling the site, and causing errors by mixing and matching url strings. This will be less of an issue on newer applications that are more robust, but do you have any method for filtering out web bots that throw errors, from a real human's request?

PS. Let us know how things work out with Tricia.

Reply to this Comment

@Toby/Ben:

One other thing to keep in mind: the more complex your error handling script, the more chances an error might occur in the handling script itself--therefore never sending you a notification.

For example, let's say your drive is getting "out of disk space" errors. If you happen to be writing your error logs to the same disk, you'll never get those notifications because your error handling script is throwing an error--thus not running.

Just something to keep in mind.

Reply to this Comment

@Bash,

Ha ha ha ha.

@Tim,

I suppose you can update the error handling to perhaps check for certain things?

@Dan,

I would try not to think of errors within the error handler because hopefully, those have been ironed out! As far as running out of disk space, I can't believe I can actually say this, but I have gotten into that situation :) Usually, when that happens though, not even SQL can run (if its on the same disk) and the site basically dies. To be honest, I am not sure I even have a good idea for that other than to monitor your disk space better (and not have a small machine).

Reply to this Comment

Does the onError method in application.cfc, which I have never used, allow you to provide more information then the standard info via cferror?

Just wanted to be make sure I understood that, because it would be really nice to get form, url, scoped data.

Reply to this Comment

@Craig,

The onError() method gets the exception object passed to it as an argument (CFArgument). This will give you the full exception information including the stack trace and tag context and the type of error and all that goodness.

Inside the onError() event handler, however, you have access to all of the normal ColdFusion features. As such, you will have access to the URL and FORM and CGI and COOKIE scopes and all the others as well.

I didn't do it in this demo but, when I CFDump scopes to the error email, I *do* typically include the URL, FORM, and CGI scopes.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.