initial
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
Aaron Greenlee
|
||||
http://aarongreenlee.com/
|
||||
|
||||
This work is licensed under a Creative Commons Attribution-Share-Alike 3.0
|
||||
Unported License.
|
||||
|
||||
// Original Info -----------------------------------------------------------
|
||||
Author : Aaron Greenlee
|
||||
Created : 10/01/2010
|
||||
|
||||
HothTracker is responsible for accepting a exception, tracking the frequency
|
||||
of the exception and alerting developers for new, unique exceptions.
|
||||
|
||||
// Modifications :---------------------------------------------------------
|
||||
Modified : 12/13/2010 9:52:41 AM by Aaron Greenlee.
|
||||
- Now supporting ColdBox 3.0 RC1
|
||||
*/
|
||||
component
|
||||
name='HothTracker'
|
||||
accessors=false
|
||||
{
|
||||
|
||||
public Hoth.HothTracker function init (HothConfig) {
|
||||
// If a config object was not provided we
|
||||
// will use our default.
|
||||
variables.Config = (structKeyExists(arguments, 'HothConfig'))
|
||||
? arguments.HothConfig
|
||||
: new Hoth.object.HothConfig();
|
||||
|
||||
VARIABLES._NAME = 'Hoth_' & variables.Config.getApplicationName();
|
||||
|
||||
variables.exceptionKeys = ['detail','type','tagcontext','stacktrace','message']; // Required exception keys
|
||||
variables.paths.LogPath = variables.Config.getLogPathExpanded(); // Get the root location for our logging.
|
||||
variables.paths.Exceptions = variables.Config.getPath('exceptions'); // Track the unique exceptions.
|
||||
variables.paths.Incidents = variables.Config.getPath('incidents'); // Track the hits per exception.
|
||||
variables.paths.Report = variables.Config.getPath('exceptionReport'); // The actual report
|
||||
variables.paths.Activity = variables.Config.getPath('exceptionReportActivity'); // Track when we save things. Helps understand volume.
|
||||
//variables.paths.Index = variables.Config.getPath('exceptionIndex'); // Tracks the exception keys to prevent duplication
|
||||
|
||||
verifyDirectoryStructure();
|
||||
|
||||
try {
|
||||
// For the life of this HothTracker, keep the ApplicationManager.
|
||||
variables.HothApplicationManager = new Hoth.object.HothApplicationManager(HothConfig);
|
||||
// Because the entire application db file is locked, we only want to
|
||||
// learn about this application when the HothTracker is created. The
|
||||
// actual amount of work done within the manager is small, but, it is
|
||||
// does block all other processes for all other Hoth instances, so, we
|
||||
// want to reduce calls to it to this constructor.
|
||||
variables.HothApplicationManager.learnApplication(arguments.HothConfig);
|
||||
} catch (any e) {
|
||||
//writeDump(e);abort;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Track an exception.
|
||||
@ExceptionStructure A ColdFusion cfcatch or a supported object from a Framework or Application. */
|
||||
public boolean function track (any Exception) {
|
||||
local.ExceptionStructure = parseException(arguments.Exception);
|
||||
|
||||
// If we did not parse what we are supposed to
|
||||
// track, we will abort.
|
||||
if (!local.ExceptionStructure.validException)
|
||||
return false;
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
try {
|
||||
// ------------------------------------------------------------------------------
|
||||
local.e = {
|
||||
detail = structKeyExists(local.ExceptionStructure,'detail') ? local.ExceptionStructure.detail : '_noDetail'
|
||||
,message = structKeyExists(local.ExceptionStructure,'message') ? local.ExceptionStructure.message : '_noMessage'
|
||||
,stack = structKeyExists(local.ExceptionStructure,'stacktrace') ? local.ExceptionStructure.stacktrace : '_no_stacktrace'
|
||||
,context = structKeyExists(local.ExceptionStructure,'tagcontext') ? local.ExceptionStructure.tagcontext : '_no_tagcontext'
|
||||
,format = structKeyExists(local.ExceptionStructure,'format') ? local.ExceptionStructure.format: '_no_format'
|
||||
,url = CGI.HTTP_HOST & CGI.path_info
|
||||
,client = CGI.HTTP_USER_AGENT
|
||||
};
|
||||
|
||||
// Generate JSON for hashing
|
||||
local.json = {};
|
||||
local.k = '';
|
||||
for(local.k in local.e)
|
||||
local.json[k] = serializeJSON(local.e[local.k]);
|
||||
|
||||
// Hash a unique key for the content of each property within the exception
|
||||
local.index = {};
|
||||
local.index.stack = ( len(local.e.stack) > 0) ? hash(lcase(local.e.stack),'SHA') : '_no_stack';
|
||||
local.index.key = lcase(local.index.stack);
|
||||
|
||||
local.saveDetails = false;
|
||||
|
||||
// Index the exception, count occurances and save details.
|
||||
// Lock is unique to the exception.
|
||||
lock name=local.index.key timeout=variables.Config.getTimeToLock() type="exclusive" {
|
||||
local.filename = local.index.key & '.log';
|
||||
local.exceptionFile = variables.paths.Exceptions & '/' & local.filename;
|
||||
local.incidentsFile = variables.paths.Incidents & '/' & local.filename;
|
||||
|
||||
local.exceptionIsKnown = fileExists(local.exceptionFile);
|
||||
|
||||
if (!local.exceptionIsKnown)
|
||||
fileWrite(local.exceptionFile ,serializeJSON(local.e),'UTF-8');
|
||||
|
||||
// Create an incident if the file does not exist
|
||||
if (!fileExists(local.incidentsFile)) {
|
||||
fileWrite(local.incidentsFile ,now() & '#chr(13)#','UTF-8');
|
||||
} else {
|
||||
local.file = fileOpen(local.incidentsFile,'append','utf-8');
|
||||
fileWriteLine(local.file, now() & '|' & local.e.url );
|
||||
fileClose(local.file);
|
||||
}
|
||||
}
|
||||
|
||||
// Outside the lock, send mail if requested
|
||||
if (!local.exceptionIsKnown && variables.Config.getEmailNewExceptions() ) {
|
||||
|
||||
local.INetAddress = createObject( 'java', 'java.net.InetAddress' );
|
||||
|
||||
local.url = (len(CGI.QUERY_STRING) > 0)
|
||||
? CGI.http_host & CGI.path_info & '?' & Cgi.QUERY_STRING
|
||||
: CGI.http_host & CGI.path_info;
|
||||
|
||||
local.emailBody = [
|
||||
"Hoth tracked a new exception (" & local.index.key & ")."
|
||||
,"Message: " & local.e.message
|
||||
,"Machine Name: " & local.INetAddress.getLocalHost().getHostName()
|
||||
,"Address: " & local.url
|
||||
];
|
||||
|
||||
local.Mail = new Mail( subject='Hoth Exception (' & variables.Config.getApplicationName() & ') ' & local.index.key
|
||||
,to=variables.Config.getEmailNewExceptionsTo()
|
||||
,from=variables.Config.getEmailNewExceptionsFrom());
|
||||
|
||||
// Attach the file
|
||||
if ( variables.Config.getEmailNewExceptionsFile() ) {
|
||||
local.Mail.addParam(file=local.exceptionFile);
|
||||
ArrayAppend( local.emailBody, "To view the attached exception info, copy and paste into FireBug's console (x = exception) and press CRTL+Enter." );
|
||||
}
|
||||
|
||||
// Show exception as HTML inline?
|
||||
if ( variables.Config.getEmailExceptionsAsHTML() ) {
|
||||
local.Mail.setType( "html" );
|
||||
savecontent variable="local.emailBody" {
|
||||
writeOutput( arrayToList( local.emailBody, "<br />" ) );
|
||||
writeOutput( "<br />Exception Details:<br />");
|
||||
writeDump( local.e );
|
||||
}
|
||||
local.Mail.setBody( local.emailBody );
|
||||
}
|
||||
else {
|
||||
local.Mail.setBody( arrayToList( local.emailBody, "#chr(10)##chr(13)#" ) );
|
||||
}
|
||||
|
||||
local.mail.Send();
|
||||
}
|
||||
// ------------------------------------------------------------------------------
|
||||
} catch (any e) {
|
||||
return false;
|
||||
}
|
||||
// ------------------------------------------------------------------------------
|
||||
return true;
|
||||
}
|
||||
|
||||
// Private Methods Follow -------------------------------------------------------
|
||||
/** Parse an exception provided by a framework or supported application.
|
||||
Hoth is easy to support. Just provide a Struct with at least the following keys:
|
||||
'detail,type,tagContext,StackTrace,Message'
|
||||
Or an object with the same information that Hoth can extract. */
|
||||
private struct function parseException(any Exception) {
|
||||
local.result = { validException = false };
|
||||
|
||||
// Return the meta data for our passed exception
|
||||
local.md = getMetaData(Exception);
|
||||
|
||||
// Inspect the class tree to understand what type of object
|
||||
// we are working with.
|
||||
local.classTree = GetClassHeirarchy(local.md);
|
||||
|
||||
// Was our object built with ColdFusion? If so, it is custom.
|
||||
// If not, the object is considered to be native and generated by CF.
|
||||
local.exceptionType = (
|
||||
listContainsNoCase(local.classTree, 'coldfusion.runtime.AttributeCollection', ' ')
|
||||
) ? 'Custom' : 'Native';
|
||||
|
||||
if (local.exceptionType == 'Native')
|
||||
{
|
||||
local.result.detail = (structKeyExists(arguments.Exception, 'detail')) ? arguments.Exception.detail : 'undefined';
|
||||
local.result.message = (structKeyExists(arguments.Exception, 'message')) ? arguments.Exception.message : 'undefined';
|
||||
local.result.stacktrace = (structKeyExists(arguments.Exception, 'stacktrace')) ? arguments.Exception.stacktrace : 'undefined';
|
||||
local.result.tagcontext = (structKeyExists(arguments.Exception, 'tagContext')) ? arguments.Exception.tagContext : 'undefined';
|
||||
local.result.validException = true;
|
||||
local.result.format = 'Native';
|
||||
|
||||
// ADDED by Benoit Hediard to get real detail and message in FW1
|
||||
if (local.result.message == "Event handler exception." && structKeyExists(arguments.Exception, "Cause")) {
|
||||
local.result.detail = (structKeyExists(arguments.Exception.Cause, 'detail')) ? arguments.Exception.Cause.detail : 'undefined';
|
||||
local.result.message = (structKeyExists(arguments.Exception.Cause, 'message')) ? arguments.Exception.Cause.message : 'undefined';
|
||||
};
|
||||
return local.result;
|
||||
} else {
|
||||
//detail,type,tagcontext,stacktrace,message
|
||||
switch(local.md.fullname) {
|
||||
// ColdBox Excepiont
|
||||
case 'coldbox.system.beans.ExceptionBean' :
|
||||
case 'coldbox.system.web.context.ExceptionBean' :
|
||||
local.result.detail = arguments.Exception.getDetail();
|
||||
local.result.message = arguments.Exception.getMessage();
|
||||
local.result.stacktrace = arguments.Exception.getStackTrace();
|
||||
local.result.tagcontext = arguments.Exception.getTagContext();
|
||||
local.result.validException = true;
|
||||
local.result.format = 'ColdBox';
|
||||
break;
|
||||
// Unknown Exception Passed
|
||||
default :
|
||||
local.result.validException = false;
|
||||
local.result.format = 'Unknown';
|
||||
break;
|
||||
/*case 'MachII.util.Exception' :
|
||||
local.result = arguments.Exception.getCaughtException();
|
||||
local.result.validException = true;
|
||||
break;*/
|
||||
}
|
||||
}
|
||||
|
||||
return local.result;
|
||||
}
|
||||
|
||||
/** Inspect the class heirarchy to return a string showing the class tree.
|
||||
Original code is borrowed from Dominic Watson at the following address.
|
||||
Rewritten for for CF9+/Railo features.
|
||||
http://fusion.dominicwatson.co.uk/2007/09/coldfusion-objects-are-java-objects.html **/
|
||||
private function GetClassHeirarchy(obj) {
|
||||
local.thisClass = obj.GetClass();
|
||||
local.sReturn = thisClass.GetName();
|
||||
|
||||
do{
|
||||
local.thisClass = local.thisClass.GetSuperClass();
|
||||
local.sReturn &= " " & thisClass.GetName();
|
||||
} while(CompareNoCase(thisClass.GetName(), 'java.lang.Object'));
|
||||
|
||||
return local.sReturn;
|
||||
}
|
||||
|
||||
/** Verify the desired directory structure exsits. */
|
||||
private void function verifyDirectoryStructure() {
|
||||
// Verify our index diectory exists
|
||||
|
||||
/* Ensure our directory structure is as expected. */
|
||||
lock name=VARIABLES._NAME timeout=variables.Config.getTimeToLock() type="exclusive" {
|
||||
if (!directoryExists(variables.paths.Exceptions)) {
|
||||
directoryCreate(variables.paths.Exceptions);
|
||||
fileWrite(variables.paths.Exceptions & '/_readme.txt','Hoth: The files within this directory contain the complete details for each unique exception.');
|
||||
}
|
||||
if (!directoryExists(variables.paths.Incidents)) {
|
||||
directoryCreate(variables.paths.Incidents);
|
||||
fileWrite(variables.paths.Incidents & '/_readme.txt','Hoth: The files within this directory contain the details about the volume of errors for each unique exception.');
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user