A client sent me a set of exception logs and asked me to make fixes where I could. I have a limited set of hours so I needed to focus on the errors that occurred most often. I wrote up a quick ColdFusion script that would parse the exception logs and keep count of unique errors. Here is the code I came up with along with some explanation as to how it works.

First, I specified a list of files. This could be a cfdirectory call too I suppose:

<!--- list of logs to parse ---> <cfset logs = "/Users/ray/Desktop/fwfeedsubmission/examples-sjc1-3_exception.log,/Users/ray/Desktop/fwfeedsubmission/examples-sjc1-4_exception.log">

I then created a structure to store unique errors:

<cfset errors = {}>

Next, I looped over each file and each line in the file:

<cfloop index="logfile" list="#logs#"> <cfloop index="line" file="#logFile#">

Exception logs have 'blocks' of errors where one line looks like a standard CFML log and is then followed by more details and a stack track. So for example:

"Error","jrpp-541","06/21/09","20:24:31",,"Context validation error for the cfmail tag.The start tag must have a matching end tag. An explicit end tag can be provided by adding. If the body of the tag is empty, you can use the shortcut. The specific sequence of files included or processed is: /Library/WebServer/Documents/test3.cfm, line: 3 "
coldfusion.compiler.UnmatchedStartTagException: Context validation error for the cfmail tag.
at coldfusion.compiler.cfml40.start(cfml40.java:2769)
at coldfusion.compiler.NeoTranslator.parsePage(NeoTranslator.java:503)

So my code simply says - look for "Error", in front, and if so, get the item:

<!--- only use if line begins with "Error", ---> <cfif find("""Error"",", line)> <!--- convert to array, keeping nulls ---> <cfset arr = listToArray(line, "," , true)> <!--- remove 1-5 ---> <cfloop index="x" from="1" to="5"> <cfset arrayDeleteAt(arr, 1)> </cfloop>

<cfset errorLog = arrayToList(arr, " ")> <cfif not structKeyExists(errors, errorLog)> <cfset errors[errorLog] = 0> </cfif> <cfset errors[errorLog]++> </cfif>

What's with the funky listToArray/arrayToList? Well some of the error detail messages includes commas, but the first 5 items never do. So I convert the line to an array and tell it to include empty items. I then delete the first 5. I'm not left with N items, where N is dependent on how many commas were in the message. I convert it back to a list with a space delimiter and I'm good to go.

Next I wrap the loops:

</cfloop> </cfloop>

Reporting is as simple as doing a structSort and displaying an ugly table:

<cfset sorted = structSort(errors,"numeric","desc")> <table border="1"> <tr> <th>Error</th> <th>Count</th> </tr> <cfloop index="k" array="#sorted#"> <cfoutput> <tr> <td>#k#</td> <td>#numberFormat(errors[k])#</td> </tr> </cfoutput> </cfloop> </table>

Here is some sample output from my local exception.log:

Enjoy. The complete script may be found here:

<!--- list of logs to parse ---> <cfset logs = "/Users/ray/Desktop/fwfeedsubmission/examples-sjc1-3_exception.log,/Users/ray/Desktop/fwfeedsubmission/examples-sjc1-4_exception.log,/Users/ray/Desktop/fwfeedsubmission/examples-sjc1-5_exception.log,/Users/ray/Desktop/fwfeedsubmission/examples-sjc1-6_exception.log">

<cfset errors = {}> <cfloop index="logfile" list="#logs#"> <cfloop index="line" file="#logFile#"> <!--- only use if line begins with "Error", ---> <cfif find("""Error"",", line)> <!--- convert to array, keeping nulls ---> <cfset arr = listToArray(line, "," , true)> <!--- remove 1-5 ---> <cfloop index="x" from="1" to="5"> <cfset arrayDeleteAt(arr, 1)> </cfloop>

<cfset errorLog = arrayToList(arr, " ")> <cfif not structKeyExists(errors, errorLog)> <cfset errors[errorLog] = 0> </cfif> <cfset errors[errorLog]++> </cfif> </cfloop> </cfloop>

<cfset sorted = structSort(errors,"numeric","desc")> <table border="1"> <tr> <th>Error</th> <th>Count</th> </tr> <cfloop index="k" array="#sorted#"> <cfoutput> <tr> <td>#k#</td> <td>#numberFormat(errors[k])#</td> </tr> </cfoutput> </cfloop> </table>