initial
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
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 : 01/12/2011
|
||||
|
||||
HothReporter offers a simple Web UI to report the errors observed by Hoth.
|
||||
|
||||
// Modifications :---------------------------------------------------------
|
||||
|
||||
*/
|
||||
component
|
||||
name='HothReporter'
|
||||
accessors=false
|
||||
output="false"
|
||||
{
|
||||
public Hoth.HothReporter function init (HothConfig)
|
||||
{
|
||||
// If a config object was not provided we
|
||||
// will use our default.
|
||||
variables.Config = (structKeyExists(arguments, 'HothConfig'))
|
||||
? arguments.HothConfig
|
||||
: new Hoth.config.HothConfig();
|
||||
|
||||
VARIABLES._NAME = 'Hoth_' & variables.Config.getApplicationName();
|
||||
|
||||
variables.exceptionKeys = ['detail','type','tagcontext','stacktrace','message'];// Required exception keys
|
||||
variables.logPathIsRelative = variables.Config.getLogPathIsRelative();
|
||||
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
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Quick report. Really a work in process.
|
||||
* @exception Accepts a hash value or 'all'
|
||||
**/
|
||||
public struct function report (required string exception)
|
||||
{
|
||||
if (arguments.exception=='all')
|
||||
{
|
||||
return generateExceptionIndex();
|
||||
} else {
|
||||
local.filepath = variables.paths.Exceptions & '/' & arguments.exception;
|
||||
|
||||
local.exception = (fileExists(local.filepath))
|
||||
? fileRead(local.filepath)
|
||||
: serializeJSON ({'message'="That report no longer exists."});
|
||||
|
||||
return deserializeJSON (local.exception);
|
||||
}
|
||||
}
|
||||
|
||||
/** Quick report. Really a work in process.
|
||||
* @exception Accepts a hash value or 'all'
|
||||
**/
|
||||
public array function delete (required string exception)
|
||||
{
|
||||
|
||||
local.response = [];
|
||||
if (arguments.exception == 'all')
|
||||
{
|
||||
lock name=VARIABLES._NAME timeout=variables.Config.getTimeToLock() type="exclusive" {
|
||||
directoryDelete(variables.paths.Exceptions, true);
|
||||
directoryDelete(variables.paths.Incidents, true);
|
||||
directoryCreate(variables.paths.Exceptions);
|
||||
directoryCreate(variables.paths.Incidents);
|
||||
}
|
||||
} else { local.exceptionPath = variables.paths.Exceptions & '/' & arguments.exception;
|
||||
local.incidentPath = variables.paths.Incidents & '/' & arguments.exception;
|
||||
|
||||
local.response = [];
|
||||
|
||||
if (fileExists(local.exceptionPath))
|
||||
{
|
||||
fileDelete(local.exceptionPath);
|
||||
arrayAppend(local.response, "Exception file deleted.");
|
||||
} else {
|
||||
arrayAppend(local.response, "Exception file did not exist!");
|
||||
}
|
||||
if (fileExists(local.incidentPath))
|
||||
{
|
||||
fileDelete(local.incidentPath);
|
||||
arrayAppend(local.response, "Incident record file deleted.");
|
||||
} else {
|
||||
arrayAppend(local.response, "Incident record file not exist!");
|
||||
}
|
||||
return local.response;
|
||||
}
|
||||
}
|
||||
|
||||
/** Return the report's view **/
|
||||
public string function getReportView () {
|
||||
local.view = fileRead(expandPath('/Hoth') & '/views/report.html');
|
||||
|
||||
// Replace the Hoth URL
|
||||
return replaceNoCase(
|
||||
local.view
|
||||
,'${HOTH_REPORT_URL}'
|
||||
,variables.Config.getHothReportURL());
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
private struct function generateExceptionIndex() {
|
||||
// Read our file system
|
||||
local.exceptions = directoryList (variables.paths.Exceptions,false);
|
||||
local.incidents = directoryList (variables.paths.Exceptions,false);
|
||||
|
||||
local.report = {};
|
||||
for (i=1;i LTE ArrayLen(local.exceptions);i=i+1) {
|
||||
|
||||
local.instance = {};
|
||||
local.instance.filename =
|
||||
listLast(local.exceptions[i],'\/');
|
||||
|
||||
if (left(local.instance.filename, 1) != '_')
|
||||
{
|
||||
//local.instance.exceptionDetail =
|
||||
//fileRead (local.exceptions[i]);
|
||||
|
||||
if (!fileExists(variables.paths.Incidents & '/' & local.instance.filename))
|
||||
{
|
||||
local.instances = '';
|
||||
} else {
|
||||
local.instances =
|
||||
fileRead(variables.paths.Incidents & '/' & local.instance.filename);
|
||||
}
|
||||
|
||||
local.instance.incidentcount = listLen(local.instances,chr(10));
|
||||
|
||||
// Save our report
|
||||
local.report[local.instance.filename] = local.instance;
|
||||
}
|
||||
}
|
||||
|
||||
return local.report;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// This CFC offers no protection--thus, anyone can access this.
|
||||
// You can implement this by renaming the CFC with a UUID--that can secure
|
||||
// this through obscurity. But, I prefer the approach shown in the ColdBox
|
||||
// example. That is what I use. I have not tried this code :(
|
||||
|
||||
component
|
||||
{
|
||||
/** Loads the Web UI (HTML) **/
|
||||
remote function index () returnformat='plain' {
|
||||
local.HothReport = new Hoth.HothReporter( new config.HothConfig() );
|
||||
return local.HothReport.getReportView();
|
||||
}
|
||||
|
||||
/** Access Hoth report data as JSON.
|
||||
@exception If not provided a list of exceptions will be returned.
|
||||
If provided, the value should be an exception hash which
|
||||
modified the behavior to return information for only
|
||||
that exception. **/
|
||||
remote function report (string exception) returnformat='JSON' {
|
||||
local.report = (structKeyExists(arguments, 'exception')
|
||||
? arguments.exception
|
||||
: 'all');
|
||||
|
||||
local.HothReport = new Hoth.HothReporter( new config.HothConfig() );
|
||||
return local.HothReport.report(local.report);
|
||||
}
|
||||
|
||||
/** Delete a report. **/
|
||||
remote function delete (string exception)returnformat='JSON' {
|
||||
if (!structKeyExists(arguments, 'exception'))
|
||||
{
|
||||
// We can delete all exceptions at once!
|
||||
arguments.exception = 'all';
|
||||
}
|
||||
|
||||
local.HothReport = new Hoth.HothReporter( new config.HothConfig() );
|
||||
|
||||
// Delete!
|
||||
return local.HothReporter.delete(arguments.exception);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
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
|
||||
|
||||
Default configuration for Hoth.
|
||||
|
||||
*/
|
||||
component
|
||||
implements='Hoth.object.iHothConfig'
|
||||
extends='Hoth.object.CoreConfig'
|
||||
accessors=true {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// BASIC HOTH SETTINGS (required)
|
||||
// -------------------------------------------------------------------------
|
||||
/** What is the name of your application? */
|
||||
property name='applicationName' default='HothDefaultConfig';
|
||||
|
||||
/** How many seconds should we lock file operations?
|
||||
For most operations this is exclusive to a unique exception. */
|
||||
property name='timeToLock' default='1';
|
||||
|
||||
/** Where would you like Hoth to save exception data?
|
||||
This folder should be empty when you start. */
|
||||
property name='logPath' default='/Hoth/examples/Example_Logs';
|
||||
|
||||
/** Is the log file location relative to the webroot?
|
||||
This folder should be empty when you start. */
|
||||
property name='logPathIsRelative' default='true';
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// HOTH EMAIL SETTINGS (required)
|
||||
// -------------------------------------------------------------------------
|
||||
/** Would you like new exceptions to be emailed to you? */
|
||||
property name='EmailNewExceptions' default='false';
|
||||
|
||||
/** What address(es) should receive these e-mails? */
|
||||
property name='EmailNewExceptionsTo' default='you@yourdomain.com';
|
||||
|
||||
/** What address would you like these emails sent from? */
|
||||
property name='EmailNewExceptionsFrom' default='hoth@yourdomain.com';
|
||||
|
||||
/** Would you like the raw JSON attached to the e-mail? */
|
||||
property name='EmailNewExceptionsFile' default='false';
|
||||
|
||||
/** Would you like HTML emails which contain the exception? */
|
||||
property name='EmailExceptionsAsHTML' default='false';
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// HOTH REPORT SETTINGS (required)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** How you access the Hoth reports is up to you. When the reports are
|
||||
generated Hoth needs to know how to build links so you can navigate
|
||||
the report and get information from the server.
|
||||
|
||||
You are responsible for deciding how you wish to access reports. Once
|
||||
you have figured that out, please, write the full URL here.
|
||||
**/
|
||||
property
|
||||
name='HothReportURL'
|
||||
default='http://office/lib/Hoth/examples/ColdFusion/HothReportUI.cfc';
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
public function init()
|
||||
{
|
||||
super.init();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/*.hoth
|
||||
@@ -0,0 +1,3 @@
|
||||
This folder is populated by Hoth to keep track of all applications that
|
||||
use this instance of Hoth so Hoth can find the log files for your
|
||||
applications when generating reports.
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
--------------------------------------------------------------------
|
||||
I configure and load Hoth into the ColdBox cache
|
||||
--------------------------------------------------------------------
|
||||
Define settings in ColdBox.cfc for example:
|
||||
interceptors = [
|
||||
//Autowire
|
||||
{
|
||||
class="coldbox.system.interceptors.Autowire",
|
||||
properties={}
|
||||
},
|
||||
//SES
|
||||
{
|
||||
class="coldbox.system.interceptors.SES",
|
||||
properties={}
|
||||
},
|
||||
//HothTracker
|
||||
{
|
||||
class="hoth.extras.ColdBoxInterceptor",
|
||||
properties={
|
||||
EmailNewExceptions = true,
|
||||
EmailNewExceptionsFile = true,
|
||||
EmailNewExceptionsTo = "kaboom@mysite.com",
|
||||
EmailNewExceptionsFrom = "server@mysite.com"
|
||||
}
|
||||
}
|
||||
];
|
||||
--------------------------------------------------------------------
|
||||
*/
|
||||
component
|
||||
{
|
||||
|
||||
/* CONSTRUCTOR
|
||||
------------------------------------------- */
|
||||
|
||||
/**
|
||||
* This is the configuration method for the interceptor
|
||||
*/
|
||||
void function Configure()
|
||||
{
|
||||
// check for required settings
|
||||
if ( !propertyExists( "EmailNewExceptionsTo" ) )
|
||||
{
|
||||
getPlugin( "logger" ).fatal( "hoth.extras.ColdBoxInterceptor.Configure", "The required 'EmailNewExceptionsTo' property has not been defined" );
|
||||
throw(
|
||||
type="hoth.extras.ColdBoxInterceptor"
|
||||
, message="The required 'EmailNewExceptionsTo' property has not been defined"
|
||||
);
|
||||
}
|
||||
|
||||
// Use ColdBox settings if not specified
|
||||
if ( !propertyExists( "applicationName" ) )
|
||||
{
|
||||
setProperty( "applicationName", getSetting( "AppName" ) & " (" & getSetting( "Environment" ) & ")" );
|
||||
}
|
||||
if ( !propertyExists( "logPath" ) )
|
||||
{
|
||||
setProperty( "logPath", getSetting( "ApplicationPath" ) & "logs/hoth" );
|
||||
setProperty( "logPathIsRelative", false );
|
||||
}
|
||||
|
||||
// Optional settings
|
||||
if ( !propertyExists( "cacheKeyName" ) )
|
||||
{
|
||||
setProperty( "cacheKeyName", "hothtracker" );
|
||||
}
|
||||
}
|
||||
|
||||
/* INTERCEPTION POINTS
|
||||
------------------------------------------- */
|
||||
|
||||
void function afterAspectsLoad( required any event, required struct interceptData )
|
||||
{
|
||||
var HothConfig = "";
|
||||
var HothTracker = "";
|
||||
var key = "";
|
||||
var properties = getProperties();
|
||||
|
||||
HothConfig = new hoth.config.HothConfig();
|
||||
|
||||
for ( key in properties )
|
||||
{
|
||||
// use evaluate to dynamically called setters
|
||||
if ( StructKeyExists( HothConfig, "set" & key ) )
|
||||
{
|
||||
evaluate( "HothConfig.set#key#( properties[ key ] )" );
|
||||
}
|
||||
}
|
||||
|
||||
HothTracker = new Hoth.HothTracker( HothConfig );
|
||||
|
||||
// store in ColdBox Cache
|
||||
getColdboxOCM().set( getProperty( 'cacheKeyName' ), HothTracker, 0 );
|
||||
|
||||
getPlugin( "logger" ).info( "hoth.extras.ColdBoxInterceptor", "HothTracker configured and loaded in ColdBox cache using key '#getProperty( 'cacheKeyName' )#'" );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
This is the default location for Hoth logs.
|
||||
|
||||
You should provide a better location in your Hoth Config object that
|
||||
is unique to your application.
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
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
|
||||
|
||||
Core configuration object for Hoth. This object provides all the
|
||||
functionality for a Hoth Config Object. Individual implementations should
|
||||
extend this object and provide their own values within the property
|
||||
declaration.
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
Modified : 12/13/2010 10:04:06 AM by Aaron Greenlee.
|
||||
- Added support for global settings.
|
||||
|
||||
*/
|
||||
component
|
||||
accessors='true'
|
||||
{
|
||||
|
||||
/** Construct a configuration object for Hoth. */
|
||||
public function init ()
|
||||
{
|
||||
local.md = getMetadata(this);
|
||||
local.n = arrayLen(local.md.properties);
|
||||
for (local.i=1; local.i <= local.n; local.i++) {
|
||||
local.fn = this['set' & local.md.properties[local.i].name];
|
||||
local.fn(local.md.properties[local.i]['default']);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void function setGlobalDatabasePath(required string path) {
|
||||
variables.GlobalDatabasePath = arguments.path;
|
||||
}
|
||||
public string function getGlobalDatabasePath() {
|
||||
return (structKeyExists(variables, 'GlobalDatabasePath'))
|
||||
? variables.GlobalDatabasePath
|
||||
: '/Hoth/db/';
|
||||
}
|
||||
|
||||
/** Expands a path **/
|
||||
public string function getLogPathExpanded ()
|
||||
{
|
||||
if (!structKeyExists(variables, 'logPathIsRelative')) {
|
||||
variables.logPathIsRelative = false;
|
||||
}
|
||||
return variables.logPathIsRelative ? expandPath( getLogPath() ) : getLogPath();
|
||||
}
|
||||
|
||||
/** Return a path for Hoth. */
|
||||
public string function getPath (name)
|
||||
{
|
||||
switch (arguments.name) {
|
||||
// Sub-Directories
|
||||
case 'incidents' :
|
||||
case 'exceptions' :
|
||||
return getLogPathExpanded() & '/' & lcase(arguments.name);
|
||||
break;
|
||||
|
||||
// Files
|
||||
case 'exceptionIndex' :
|
||||
case 'exceptionReport' :
|
||||
case 'exceptionReportActivity' :
|
||||
return getLogPathExpanded() & '/' & lcase(arguments.name) & '.log';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
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 : 12/13/2010
|
||||
|
||||
Responsible for helping Hoth report for multiple applications.
|
||||
*/
|
||||
component
|
||||
name = 'HothApplicationManager'
|
||||
accessors = true
|
||||
{
|
||||
property name='applications' type='array';
|
||||
|
||||
// The filename that holds application knowledge.
|
||||
VARIABLES.APP_LOCK_KEY = 'HothApplicationDBFileLock';
|
||||
|
||||
/** Constructor.
|
||||
* @HothConfig Requires a HothConfig Object.
|
||||
*/
|
||||
public Hoth.object.HothApplicationManager function init(
|
||||
required HothConfig
|
||||
){
|
||||
|
||||
setApplications( loadApplicationsFromDisk(HothConfig) );
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/** To allow for easy reporting, Hoth needs to be able to track new
|
||||
applications (or really, new log paths). When a new instance of
|
||||
HothTracker is created, this method will be called. **/
|
||||
public boolean function learnApplication(
|
||||
required HothConfig
|
||||
){
|
||||
// Prevent any other threads from saving our new application
|
||||
lock
|
||||
name=VARIABLES.APP_LOCK_KEY
|
||||
timeout=arguments.HothConfig.getTimeToLock()
|
||||
type="exclusive"
|
||||
{
|
||||
local.knownApplications =
|
||||
loadApplicationsFromDisk(argumentCollection=arguments);
|
||||
|
||||
for(local.index in local.knownApplications)
|
||||
{
|
||||
// Confirm our keys exist
|
||||
if (structKeyExists(local.index, 'logPath'))
|
||||
{
|
||||
// If we find the same logPath, we know about this
|
||||
// application and need to exit.
|
||||
if (local.index.logPath == arguments.HothConfig.getLogPath())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We did not find out application path within the file.
|
||||
// Create the struct and append it to our array.
|
||||
local.applicationDataToSave =
|
||||
{
|
||||
'applicationName' = arguments.HothConfig.getApplicationName()
|
||||
,'logPath' = arguments.HothConfig.getLogPath()
|
||||
,'created' = now()
|
||||
};
|
||||
arrayAppend(
|
||||
local.knownApplications
|
||||
,local.applicationDataToSave
|
||||
);
|
||||
|
||||
// Save the contents of the entire array, replacing the entire file
|
||||
local.path = arguments.HothConfig.getGlobalDatabasePath();
|
||||
fileWrite(
|
||||
expandPath(arguments.HothConfig.getGlobalDatabasePath() & 'applications.hoth')
|
||||
,serializeJSON(local.knownApplications)
|
||||
,'UTF-8'
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Loads applications already observed by Hoth from disk.
|
||||
* This process should be locked by the calling method.
|
||||
* @HothConfig Requires a HothConfig Object. */
|
||||
private array function loadApplicationsFromDisk (
|
||||
required HothConfig
|
||||
){
|
||||
local.path = arguments.HothConfig.getGlobalDatabasePath();
|
||||
local.appfile = expandPath(arguments.HothConfig.getGlobalDatabasePath() & 'applications.hoth');
|
||||
|
||||
// Return an empty array if the appFile does not exist.
|
||||
if (!fileExists( local.appfile ) )
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
local.applicationsFound = fileRead( local.appfile );
|
||||
|
||||
// Delete the database if the file is invalid.
|
||||
if (!isJSON(local.applicationsFound))
|
||||
{
|
||||
fileDelete( local.appfile );
|
||||
return [];
|
||||
}
|
||||
|
||||
// Convert the file to our desired structure
|
||||
local.parsedDB = deserializeJSON(local.applicationsFound);
|
||||
|
||||
return local.parsedDB;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
component
|
||||
{
|
||||
/**
|
||||
* Returns the number of seconds since UTC January 1, 1970, 00:00:00
|
||||
* (Epoch time).
|
||||
*
|
||||
* @param DateTime Date/time object you want converted to
|
||||
* Epoch time.(Required)
|
||||
*
|
||||
* @return Returns a numeric value.
|
||||
* @author Rob Brooks-Bilson (rbils@amkor.com)
|
||||
* @version 1, June 21, 2002
|
||||
*/
|
||||
function GetEpochTimeFromLocal() {
|
||||
local.datetime = 0;
|
||||
|
||||
if ( arrayLen(arguments) == 0)
|
||||
{
|
||||
local.datetime = Now();
|
||||
} else {
|
||||
local.datetime = arguments[1];
|
||||
}
|
||||
|
||||
return
|
||||
DateDiff(
|
||||
"s"
|
||||
,DateConvert("utc2Local", "January 1 1970 00:00")
|
||||
,datetime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
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
|
||||
|
||||
Interface for a Hoth Config Object.
|
||||
|
||||
*/
|
||||
interface {
|
||||
public function init ();
|
||||
public string function getLogPathExpanded();
|
||||
public string function getPath (name);
|
||||
}
|
||||
Reference in New Issue
Block a user