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>