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>