-
Notifications
You must be signed in to change notification settings - Fork 27
Home
Content last updated 7/7/2014
If you answer "not many" you may not know enough about what is happening with your application. I love building Web applications--specifically, I build applications that deliver a great experience and quality service. In the game I play even a single error is one error to many. I am an advocate of test-driven development and I build all sorts of unit/integration tests to confirm my applications behave as expected. But, Web applications are constantly evolving and the way clients interact with our services constantly changing. Errors happen. They just happen. Sometimes it's a hardware failure and sometimes it is a bug in your code. I've been searching for a way to easily parse logs to pluck the relevant data. I've looked at logging products and definitely see their value; but, none solved my problem the way I required.
Hoth is a lightweight, framework agnostic solution to understanding the answers to two important questions without wading through log files:
- Is this a new, unique error I have not seen before?
- How many times has this error occurred?
Hoth reports ColdFusion errors and optionally notifies you when a new error occurs. Hoth gives you a very simple UI that allows you to see each recorded error, how often these errors have occurred and optionally delete an error once you believe it will not occur again. Hoth can optionally e-mail you when a new error is observed so you can make a real-time decision if you need to get up off the couch and stop watching the Jersey Shore.
Hoth records all of the details of a exception every time it is given an exception it has not encountered before. By accessing Hoth's report, you can see the details of the very first error. After the first error, Hoth simply keeps count of the frequency of the errors by logging a timestamp for each occurrence.
Hoth is quite simple. You provide it with a ColdFusion Exception and Hoth will hash the stacktrace to determine if the exception has been observed before. Each unique stacktrace is considered to be a unique error. Hashing the stacktrace allows Hoth to assign unique exceptions their own ID and quickly determine if new exceptions have already been reported.
If Hoth does not recognize the stacktrace the following files will be created in a directory you define when you configure Hoth:
/exceptions/{hash}.log
The logs written to the exceptions directory contain the details of each exception. This information has been serialized into JSON.
/incidents/{hash}.log
The logs written to the incidents directory contain timestamps for each occurrence of an exception.
If Hoth recognizes the hash of a stacktrace a current timestamp will be appended to the incident/{hash}.log
file. Every exception should have a exception and incident log.
Hoth is fast! Hoth was designed to be very fast. But, if you see a way to make it faster I hope you'll make a pull request!
Hoth fails silently! Since Hoth would not do you any good if it had errors of it's own that allowed stacktraces to be seen by your visitors Hoth has been designed to fail silently. Unfortunately, this means you won't know if Hoth failed. You should test Hoth by setting a variable to some undefined variable after you have everything working to verify the logs were written and the UI is accessible to you.
Tracking errors takes very little work on your part! Implementing Hoth to track exceptions should be the same for all CFML applications.
Accessing Hoth Reports WILL take work on your part! Things become a little (but not a lot) more complex here. Reporting requires access to your server from a HTTP call. This is your responsibility to setup--and there are lots of paths you can take to implement reporting.
If you take the path of using the HothReportUI.cfc
you will definitely want to open that file and make some edits (like adding the path to your own HothConfig).
- Download or Clone Hoth from GitHub.
- Put Hoth on your server, or, in the root of your project. I recommend a
/lib
folder or somewhere inaccessible to Web visitors. - Watch and Star the repository so I know you care!
- Create/identify a directory for Hoth to write logs to.
- Create an mapping "/hoth" for the directory you installed Hoth into.
- Edit the Configuration Object (details below).
- Add Hoth anywhere you want to track exceptions (details below).
- Tweet about Hoth to spread the word!
The Hoth Configuration Object is unique for each application. In fact, it can be unique to each environment if your application code supports multiple environments.
// Begin Example
component
implements = 'Hoth.object.iHothConfig'
extends = 'Hoth.object.CoreConfig'
accessors = true
{
/** What is the name of your application? */
property
name='applicationName'
default='Amazing ColdFusion Club';
/** 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. */
property
name='logPath'
default='/your_mapping/hoth/cfmlClub';
// ------------------------------------------------------------------------------
/** Would you like new exceptions to be emailed to you? */
property
name='EmailNewExceptions'
default='true';
/** What address(es) should receive these e-mails? */
property
name='EmailNewExceptionsTo'
default='you@email.com;co-worker@email.com';
/** What address would you like these emails sent from? */
property
name='EmailNewExceptionsFrom'
default='you@email.com';
/** Would you like the raw JSON attached to the e-mail? */
property
name='EmailNewExceptionsFile'
default='true';
// ------------------------------------------------------------------------------
/**
The mapping where you would like Hoth to write it's log files.
Without this setting, Hoth will write log files to the same directory
Hoth is located within. This is not recommended as your will have content
mixed into your Hoth code.
**/
setGlobalDatabasePath(path='/logs/hoth/');
}
// End Example
Once you have completed the steps outlined above you can simply add the two highlighted lines to begin
tracking Hoth exceptions. The provided code assumes you have placed your HothConfig.cfc
in a /config
directory within your application.
**Simple Application.cfc Implementation Example**
<!--- ColdFusion Application.cfc OnError Method --->
<cffunction name="OnError" returnType="void" output="true">
<cfargument name="Except" required=true />
<cfargument type="String" name="EventName" required=true/>
<!--- Track this exception with Hoth --->
<cfset local.Hoth = new Hoth.HothTracker(new config.HothConfig())/>
<cfset local.Hoth.track(arguments.Except) />
<!--- Anything you need here ... --->
<cfreturn />
</cffunction>
Recommended Application.cfc Implementation
<cffunction name="onRequestStart" returnType="boolean" output="true">
<cfargument name="targetPage" type="string" required="true" />
<cfscript>
// Place Hoth into Application Memory.
if (!structKeyExists(application, 'HothTracker')
{
application.HothTracker =
new Hoth.HothTracker( new config.HothConfig() );
}
// anything you need here...
return true;
</cfscript>
</cffunction>
<cffunction name="OnError" returnType="void" output="true">
<cfargument name="Except" required=true />
<cfargument type="String" name="EventName" required=true/>
<cfscript>
// Create an instance of Hoth if one does not exist in the
// application scope. Hoth should exist in the Application Scope
// but, if something went wrong there we are ensured tracking.
local.HothTracker = (structKeyExists(application, 'HothTracker'))
? application.HothTracker
: new Hoth.HothTracker( new config.HothConfig() );
local.HothTracker.track(Except);
// anything you need here...
</cfscript>
</cffunction>
In addition to adding Hoth to your Application.cfc you can also add Hoth to your ColdBox Exception Handler. This will help you track exceptions caught by the ColdBox Framework.
component {
public void function preHandler(required Event)
{
var rc = Event.getCollection();
var prc = Event.getCollection(private=true);
// Only allow access for debug mode.
if (!getDebugMode())
{
redirectToNewEvent(event='home',eventObject=Event);
}
// Create a new Hoth Reporter using our "Hoth" mapping
// and pass a HothConfig object for this application. I am keeping
// the HothConfig.cfc for my application in my /config folder where
// I also keep my ColdBox, Routes, Cache and other configs.
prc.HothReporter = new Hoth.HothReporter( new config.HothConfig() );
}
public void function index (required Event) {
var rc = Event.getCollection();
var prc = Event.getCollection(private=true);
if (!Event.valueExists('exception'))
{
Event.setView(name='site/hoth',noLayout=true);
Event.renderData(type='html',data=prc.HothReporter.getReportView());
} else {
local.report = (structKeyExists(rc, 'exception')
? rc.exception
: 'all');
Event.renderData(type='json',data=prc.HothReporter.report(local.report));
}
return;
}
public void function delete (required Event) {
var rc = Event.getCollection();
var prc = Event.getCollection(private=true);
if (!structKeyExists(rc, 'exception'))
{
rc.exception = 'all';
}
local.result = prc.HothReporter.delete(rc.exception);
Event.renderData(type='json',data=local.result);
return;
}
}
Sometimes, we have a reason to suspect an error may occur in a specific area of your application. Hoth is great at watching "high risk" areas and reporting when these expected exceptions occur. For example, what if you wanted to know how many times your Twitter API is failing? If you are talking to Twitter you probably already have graceful error handling for this area of your application. Hoth can complement your application and report errors you throw--or--errors you trap.
<cftry>
<cfinvoke webservice="#webservice#" method="createAccount" returnvariable="local.result">
<cfinvokeargument name="email" value="#User.getEmail()#">
<cfinvokeargument name="password" value="#User.getPassword()#">
</cfinvoke>
<cfcatch type="any">
<cfset gracefulErrorCapture(
arguments.User
,cfcatch.faultString
) />
<cfcatch type="any">
<!--- Record this event with Hoth --->
<cfif structKeyExists(application, 'HothTracker')>
<cfset application.HothTracker.track(cfcatch) />
</cfif>
</cfcatch>
</cfcatch>
</cftry>
You can simply view the contents of the logs stored in the exceptions and incidents directory. Or, you can access Hoth through a UI. How you integrate the Hoth UI into your application is really up to you. I'll show you how I have implemented the reporting within my applications. These applications are ColdBox applications; however, the CFML is the same anywhere.
Important!
Currently, the report UI expects SES links in the following format: http://foo.bar.com/Hoth/report/exception/{id}
. Hoth was implemented within a ColdBox application; however, this should be easy to setup with any framework. The next update of Hoth will allow you to configure your URIs within the HothConfig.cfc
.
**Simple Reporting CFC Example**
// 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 :(
// You should replace the "new Hoth.config.HothConfig()" lines with the
// classname path to your HothConfig. Hoth will generate a report for the
// application's HothConfig you provide.
// -----------------------------------------------------------------------
// THIS IS AN EXAMPLE AND ONLY AN EXAMPLE
// -----------------------------------------------------------------------
// HOW YOU IMPLEMENT REPORTING IS REALLY YOUR BUSINESS SINCE NO ONE SHOULD
// ACCESS YOUR REPORT--OR KNOW HOW TO ACCESS YOUR REPORT--BUT YOU!!!!!!!!!
//
// This CFC should work right out of the box for the Example HOTH data.
// You will need to change the path to your config for sure.
//
// Feel free to add anything else you want (password/IP check?) to this file
// as it should become part of your code base.
component {
// You will definitely want to change this path...
variables.ApplicationsHothConfig = new Hoth.config.HothConfig();
/** Loads the Web UI (HTML) **/
remote function index () returnformat='plain' {
local.HothReport = new Hoth.HothReporter( new Hoth.config.HothConfig() );
return local.HothReport.getReportView(variables.ApplicationsHothConfig);
}
/** 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(variables.ApplicationsHothConfig);
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(variables.ApplicationsHothConfig);
// Delete!
return local.HothReporter.delete(arguments.exception);
}
}
**Simple Reporting Example for ColdBox**
component {
public void function preHandler(required Event)
{
var rc = Event.getCollection();
var prc = Event.getCollection(private=true);
// Only allow access for debug mode.
if (!getDebugMode())
{
redirectToNewEvent(event='home',eventObject=Event);
}
// Create a new Hoth Reporter using our "Hoth" mapping
// and pass a HothConfig object for this application. I am keeping
// the HothConfig.cfc for my application in my /config folder where
// I also keep my ColdBox, Routes, Cache and other configs.
prc.HothReporter = new Hoth.HothReporter( new config.HothConfig() );
}
public void function index (required Event) {
var rc = Event.getCollection();
var prc = Event.getCollection(private=true);
if (!Event.valueExists('exception'))
{
Event.setView(name='site/hoth',noLayout=true);
Event.renderData(type='html',data=prc.HothReporter.getReportView());
} else {
local.report = (structKeyExists(rc, 'exception')
? rc.exception
: 'all');
Event.renderData(type='json',data=prc.HothReporter.report(local.report));
}
return;
}
public void function delete (required Event) {
var rc = Event.getCollection();
var prc = Event.getCollection(private=true);
if (!structKeyExists(rc, 'exception'))
{
rc.exception = 'all';
}
local.result = prc.HothReporter.delete(rc.exception);
Event.renderData(type='json',data=local.result);
return;
}
}
Hoth is working very well for me and my team. It supports exception tracking for millions of unique visitors; but, things can always get better! The only thing I can think of for immediate improvement is to not return JSON when an exception is deleted via the UI. But, by sharing Hoth with you I hope to gain your feedback. I'm sure it could benefit from pretty charts showing the frequency of errors...
A Google Group has been setup and is a great place to discuss any trouble you may be having with Hoth.
Take a moment and provide a comment to share your thoughts. Thanks!
- Updated February 09, 2011: Added a note to draw attention that Hoth requires a mapping with proper case: i.e. "Hoth"
- Updated February 15, 2011: Added a link to the new Google Group to discuss any issues developers are having.:
- Updated March 1, 2011: Simplified the example of reporting for ColdBox.
- Updated March 11, 2011: Added a note to hopefully help you better understand that reporting takes work on your part. How you access Hoth reports is your responsibility. I have only shared examples. In fact, my own implementations are much more complex and secure.
- Updated July 7, 2014: Migrated content from aarongreenlee.com to GitHub wiki.