Attaching ColdFusion Error Reports To Avoid GMail Spam

Posted July 16, 2009 at 9:29 AM

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

 Launch code in new window » Download code as text file »

  • <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:

 Launch code in new window » Download code as text file »

  • <!--- 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.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Permalink  |  Other Searches  |  Print Page




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

Reader Comments

Jul 16, 2009 at 9:43 AM // reply »
24 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.


Jul 16, 2009 at 9:44 AM // reply »
111 Comments

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


Jul 16, 2009 at 9:49 AM // reply »
6,516 Comments

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


Jul 16, 2009 at 9:57 AM // reply »
24 Comments

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


Jul 16, 2009 at 10:00 AM // reply »
6,516 Comments

@James,

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


Jul 16, 2009 at 10:02 AM // reply »
24 Comments

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


Jen
Jul 16, 2009 at 10:07 AM // reply »
3 Comments

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?


Jul 16, 2009 at 10:09 AM // reply »
6,516 Comments

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


Jul 16, 2009 at 10:13 AM // reply »
24 Comments

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


Jul 16, 2009 at 10:15 AM // reply »
111 Comments

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.


Jul 16, 2009 at 10:19 AM // reply »
111 Comments

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.


Jul 16, 2009 at 10:22 AM // reply »
6,516 Comments

@Dan,

Definitely agree - the "Never Spam" filter option is the full-proof method.


Jen
Jul 16, 2009 at 10:34 AM // reply »
3 Comments

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


Jul 16, 2009 at 11:43 AM // reply »
1 Comments

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


Jul 16, 2009 at 12:27 PM // reply »
6,516 Comments

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


Jul 16, 2009 at 1:12 PM // reply »
18 Comments

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.


Jul 16, 2009 at 2:42 PM // reply »
3 Comments

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


Jul 16, 2009 at 3:14 PM // reply »
27 Comments

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.


Jul 16, 2009 at 4:26 PM // reply »
111 Comments

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


Jul 18, 2009 at 1:39 PM // reply »
6,516 Comments

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


Post Comment  |  Ask Ben

Recent Blog Comments
Nov 22, 2009 at 4:30 AM
jQuery Live() Method And Event Bubbling
dasegtezr ... read »
Nov 22, 2009 at 4:03 AM
jQuery Live() Method And Event Bubbling
C_fieri ... read »
Nov 22, 2009 at 1:56 AM
Learning ColdFusion 9: Using CFQuery In CFScript Can Enable SQL Injection Attacks
Why adobe would give you script equivalent of cfquery is beyond me. I love cfquery tag because it helps me wriite clean sql, and get away from the horrible jdbc queries If I wanted to write javali ... read »
Nov 22, 2009 at 1:45 AM
Streaming Text Using ColdFusion's CFContent Tag And The Variable Attribute
The reason you would want to do this is to stream. Ack json/xml files to ria clients I used thus technique before because putting json in response stream causes debugging info to come thru As well a ... read »
Nov 21, 2009 at 6:47 PM
Hal Helms - Real World Object Oriented Development, Sarasota - Day Five
@charlie griefer, Thank you.. ... read »
Nov 21, 2009 at 5:15 PM
Using ColdFusion Structures To Remove Duplicate List Values
@Jose Galdamez, Oh heh yeah I didn't paste the whole code. I should have defined the vars -- my bad. It's fixed thou. Thanks. ... read »
Nov 21, 2009 at 4:49 PM
Styling The ColdFusion 8 WriteToBrowser CFImage Output
Great work yet again Ben! Whilst I didn't use this whole code, I copied some of your regex code for a similar problem with the lack of an alt attribute and unescaped ampersands in CFIMAGE for Railo 3 ... read »
Nov 21, 2009 at 1:13 PM
My First ColdFusion Builder Extension - Encrypting And Decrypting CFM / CFC Files
@Ben, Because I am pedantic, I just want to make sure that everyone knows there is absolutely no encryption going on. There is only encoding and obfuscation. The cfencode tool only obfuscates your C ... read »