views:

1452

answers:

5

I'm writing an application in ColdFusion, and even though it is mostly stable, it errors every so often. Because it has replaced the old application already, sometimes it is the users who get errors instead of me.

I've created a nice page that people get when they get an error that sends information to me, such as error, referrer, error on which page, line number, etc.

The only thing I can't get to seem to work is getting the form data submitted if there is any. I don't know what page it errored on beforehand, so I can't just output '#form.field#'.

For example if:

form.abc = 1
form.def = 2

How can i get the variable names and value from 'form' without knowing them beforehand?

Is this document on looping over a structure somewhere along the right path?

Also, I'm looking for a good way to store that data in a database, because that is where the other information about the error is stored, and I don't really want to have to put it in an email to me.

+5  A: 

The Form scope has a FieldNames variable which will tell you what fields were submitted.

You can also do StructKeyList(Form) to get a list of the current variables in the scope. This one will also include FieldNames and any other variables that have since been added to the Form scope.

Either of these could be used inside a <cfloop index="CurField" list="#StructKeyList(Form)#"> - but there are easier ways...


If you are on CF8 you can convert the scope to a string nice and easily with serializeJson() and deserializeJson() functions, which could then be stored in a suitable database field.

If you're on CF6..7 then you can download a CFC called cfjson from riaforge which mimics these functions.


Finally, if you're on earlier versions of CF, or have an odd aversion to using JSON for storage, you can roll your own with an even simpler loop to the one hinted above - a collection loop lets you loop directly through a Structure or Scope - note that some annoying person picked 'item' rather than 'index' as the attribute for these.

Since we know Form variables are all simple objects (i.e. strings) I've elected for a basic key=value[newline]key=value[newline]... format, which is easy to reverse also.

Encoding:

<cfset Output = '' />
<cfloop item="CurField" collection="#Form#">
    <cfset Output = Output & CurField & '=' & Form[CurField] & Chr(10) />
</cfloop>

<cfoutput>#Output#</cfoutput>

Decoding:

<cfset FormData = StructNew()/>
<cfloop index="CurLine" list="#Output#" delimiters="#Chr(10)#">
    <cfset FormData[ListFirst(CurLine,'=')] = ListRest(CurLine,'=') />
</cfloop>

<cfdump var="#FormData#"/>


One last important note: As with all user-supplied variables (Form,Url,Cookie scopes) you must make sure you handle them correctly to prevent security holes - specifically make sure you are using cfqueryparam for all your database queries - don't want to sidetrack too far, but feel free to ask another question if you need any help with cfqueryparam.


Hope this helps. :)

Peter Boughton
Doh! I knew something didn't seem right there. Thanks for correcting to FieldNames - ColumnList is what queries use.
Peter Boughton
+3  A: 

Form is just a hash/structure, so you can just iterate through its keys:

<cfoutput>
 <cfloop collection=#form# item="field">
  #htmleditformat(field)=#htmleditformat(form[field])#<br/>
 </cfloop>
</cfoutput>

Putting the data into a database? How you do that depends on what you want to do with it afterwards. The most flexible solution might be to just convert the whole of form (and url) into a WDDX object, and store that in a blob field:

<cfwddx action="cfml2wddx" input=#url# output="encodedURL"/>
<cfwddx action="cfml2wddx" input=#form# output="encodedForm"/>
<cfquery datasource="yourDSN">
 INSERT INTO errorlog (datestamp, event, script_name, path_info, url, form)
 VALUES
  (<cfqueryparam value=#now()# cfsqltype="CF_SQL_TIMESTANP"/>,
   <cfqueryparam value="your error string or cfcatch data" cfsqltype="CF_SQL_VARCHAR"/>,
   <cfqueryparam value=#cgi.script_name# cfsqltype="CF_SQL_VARCHAR"/>,
   <cfqueryparam value=#cgi.path_info# cfsqltype="CF_SQL_VARCHAR"/>,
   <cfqueryparam value=#encodedURL# cfsqltype="CF_SQL_BLOB"/>,
   <cfqueryparam value=#encodedForm# cfsqltype="CF_SQL_BLOB"/>)
</cfquery>

That way you'll have access to all the raw data for analysis at your leisure. Plenty of other ways to go about it, of course.

Pete Jordan
WDDX is a very 'heavy' data format - for a simple form/error details it's probably fine, but I'd still recommend using a more lightweight format (like JSON or INI-style).
Peter Boughton
+2  A: 

if you're using the Application.cfc in your application, just replace the onError method with the one below. Note: application.settings.mode can be one of the following values (dev,test,prod). application.settings.webmasteremail should be set to the email address you want the error information mailed to. Both of these variables should be set in the onApplicationStart method.

What does this function do? If the app is set to "dev" mode (by setting application.settings.mode = "dev") it will display the error information on the screen. If the app is set to any other mode (like test or prod) it will email and store the information in a database.

<cffunction name="onError" returnType="void" output="true">
 <cfargument name="exception" required="true">
 <cfargument name="eventname" type="string" required="true">
 <!--- corrects bug where <cfabort> throw an error --->
 <cfif arguments.exception.rootCause eq "coldfusion.runtime.AbortException">
     <cfreturn>
 </cfif>
 <cfsavecontent variable="errordata">
        <p>#eventname#</p>
        <cfdump var="#exception#" label="Exception Information" format="text">
        <cfdump var="#url#" label="URL Information" format="text">
        <cfdump var="#form#" label="FORM Information" format="text">
        <cfdump var="#application#" label="Application Scope" format="text">
        <cfif isdefined("session")>
            <cfdump var="#session#" label="Session Scope" format="text">
        </cfif>
        <cfif isdefined("client")>
            <cfdump var="#client#" label="Client Scope" format="text">
        </cfif>
        <cfdump var="#cgi#" label="URL Information" format="text">
 </cfsavecontent>

 <!--- will email the error inforation and display the error page --->
 <cfif application.settings.mode eq "dev">
  <p>#eventname#</p>
  #errordata#
 <cfelse>
  <cfmail to="#application.settings.webmasteremail#" from="error@#cgi.http_host#" subject="Error has occured on #cgi.http_host#" type="HTML" charset="windows-1252">
  <p>#eventname#</p>
  #errordata#
  </cfmail>

  <cfquery datasource="yourDSN">
  insert into errorlog
  (
   creationdate
   ,event
   ,info
  )
  values
  (
   <cfqueryparam value="#now()#" cfsqltype="CF_SQL_TIMESTAMP"/>
   ,<cfqueryparam value="#eventname#" cfsqltype="CF_SQL_VARCHAR"/>
   ,<cfqueryparam value="#errordata#" cfsqltype="CF_SQL_LONGVARCHAR"/>
  )
  </cfquery>

  <h2>A functional error has occurred.</h2>
  <p>A notification of this error has been automatically e-mailed to the development team; no action on your part is required.</p>
  <p>Please click the "back" button on your browser to return to the web site.  We apologize for this inconvenience.</p>
 </cfif>
</cffunction>
rip747
A: 

If you are emailing yourself the form variables, just dump them, like so:

<cfmail type="html" ...>
    <cfdump var="#form#" label="form">
    <cfdump var="#cgi#" label="cgi">
    <cfdump var="#session#" label="session">
</cfmail>

You can dump out whatever scopes you like in there. You may or may not need those I provided.

You said your initial problem was that the application is replacing the old application? Are you developing on the same server you are deploying on? If so, you can just change the name of the application for your development copy - make sure the names are unique to avoid this problem (if that is what your problem is).

Nathan Strutz
+3  A: 

If you want to manually iterate over all form fields, the simplest way is a cfloop like so:

<cfloop collection="#form#" item="variables.name">
  #variables.name#=(#form[variables.name]#)<br/>
</cfloop>

But that said, for the purposes of an error email, you're probably going to find it's easier just to "dump" the form scope (which is just a special structure), like this:

<cfmail
  from="errors@#cgi.server_name#"
  to="[email protected]"
  subject="Error Occurred in such and such a place"
  type="html">
    <cfdump var="#form#"/>
</cfmail>

When sending error emails, I like to include a second cfdump for the CGI scope (again, just a special struct) as this can provide some other helpful information about the request.

If you have CF 8, you may also like to dump in text format, like the following, because it will make the message size smaller (and in my opinion, make the message more readable)

<cfdump var="#form#" format="text">

Note: the format attribute of the CFDump tag was added for CF 8, so you won't be able to use it in previous versions of ColdFusion.

You mentioned that you were looking for a way to store these errors in a database, and that's a good idea. Instead of rolling your own custom solution for that, I suggest you check out BugLogHQ. It's been around for a while and works well for others, including me, and best of all it's FREE and Open Source. It's age and widespread use means it will be less likely to have errors in its own code and probably has more, better features than what you would write.

Using BugLogHQ, you don't have to worry about the display of the error, you just send the data to the bug logger and it takes care of the rest.

Adam Tuttle
Never heard of BugLogHQ, but it looks to be a good application, and not difficult to integrate/extend as necessary.*** I think you should edit/split your answer to make it a clearer answer.
Peter Boughton
Rewritten to be clearer, more informative, and more all-inclusive. :)
Adam Tuttle