SlideShare a Scribd company logo
Advanced Error
Handling Strategies
By Mary Jo Sminkey
Senior Web Developer
CFWebtools, LLC
Who Am I?
• Senior Web Developer at CFWebtools, LLC
• ColdFusion Developer since CF3/Allaire
• Cosplayer (esp. Dr Who and Star Trek)
• Dog Trainer and Instructor
• Sewer/Baker/Knitter/Origamist/Ass. Crafts
• Cancer Survivor
• Fibromyalgia/Invisible Disabilities Advocate
No Site Is Going to Be Bug-Free
Do You Get Emails Like This?
“What a pathetic useless website you have. Even when I used the exact same
words you use in your product description, all I got was sorry no results for your
search.”
“your on line web site sucks ”
“this stupid website will not allow me to purchase a part I need, I go to check
out and thats as far as I can go.Thanks.”
“Fix your order web site so I can place my order or I'm moving on ”
“The forms and fields are virtually unreadable...the no contrast type against a
white background...it really sucks.Whats up with you guys? “
Or Maybe Ones Like This?
“Your website has got to be the worst website I have been to. It is so unuser friendly.
The search is useless.Very rarely do I find what I want on this website. Easier to go to
the book then find the part number. I keep coming back to it with hopes then quickly
get mad at how it just plain sucks! Sorry but it does.”
“Hi for the last few weeks every time I try to check out and pay for my order by
clicking on Billing Summary it just seems to keep loading but never lets me get to the
payment stage.. any help or advice would be appreciated as ive been trying to check
out a fairly large order for a few months now and not having any luckThankyou”
“Lost 45 minutes trying to check out my order. Could not proceed past payment page
for at least 45 minutes today. Using safari. I had to call C.S. for help but then it finally
just went through.”
Or My Personal Favorite!
“Hire a new IT leader !Your site sucksASS. Could not make a order with out a
password reset. No F, way. Call during business hours. Really ? How much $$
are you loosing because of your IT dumb ass ?Think about it. Hope these
emails get past you to some one with a stake in the company. ”
Yes – these really are actual emails that have come into the
webmaster account for one of the sites I have worked with.
And no – I’m really not bad at my job! 🌞
Do These Just Go Into Your Trash Folder?
• It’s tempting to just trash complaints particularly when they offer little
detail about the problem (and especially when they often come across as a
personal attack on your skills).
• But regardless of the tone of the complaints you get, you have to assume
that there are many other customers probably experiencing the same issues
but that didn’t bother to email you, they just left for another site where they
aren’t having a problem.
• A developer that has the best interest of their client in mind needs to find
ways to figure out the reason these users are unhappy and SOLVE those
problems.
Other Advantages to Good Error Tracking
• Many errors don’t get reported at all.
• If you keep a good eye on the errors that come through, you can often push
out quick fixes before your client even reports them to you. Big bonus points
with clients.
• If your error handling captures end user data, you can do the same thing, fix
the error the user encountered and then email them to let them know it’s
been fixed. Huge bonus points again with end users that you are on top of
the issues this fast.
• Often error reports can give you heads-up on hack attempts coming into
the site that you might need to block.
How Do We Get More Information from the User?
• If you’ve ever tried replying to an email like these to get more information from the
customer, you know how fruitless that often is.
• Customers always assume that the issue they are having is happening for
everyone, developers know thatVERY often isn’t true.
• Many problems are due to customers doing things we don’t even expect, like
misspelling words in their search, and not figuring that out when the search
doesn’t work.
• Many times a request for more information just results in more angry replies
questioning your competence and your mother’s parentage, and most users have
no clue about things like the error console in their browser.
• We need to get information from the user *without* their help.
Ways to Collect User Data When Problems Occur
• Global Error Handler is a bare minimum (cferror template)
• Track User Actions in Session
• Use BugTracking Systems
• Log Errors (cflog, SQL error logging)
Global Error Handler
• Cferror vs. using an OnError
• Allows you to dump all the available scopes
• Able to include into a cfcatch block
• Use Application variable to track number of instances of errors (prevent
emailing lots of copies of same error)
• Additional emails when we detect large number of errors occurring
• Clean the scopes of sensitive data elements (or very large data structures
we don’t need).
• Logging of errors we don’t want to email (db timeouts, etc.)
Global Error Handler – More Info
• Blog Articles at ColdFusionMuse
• http://www.coldfusionmuse.com/index.cfm/2014/2/11/Building-a-Robust-Error-Handler
• http://www.coldfusionmuse.com/index.cfm/2017/7/12/robust-error-handling-part2
• NCDevCon 2015 Presentation
• https://www.youtube.com/watch?v=c0dQfBoCCmo
Page Tracking
• First thing we want to know when a user has problems…what URLs have
they clicked through before reporting it
• SessionTracker for page clicks
• Run on RequestStart so each page logged automatically
• Easily modify number of pages to include, etc.
• Clear out old pages as new pages added to tracker
• Excluding Pages that clog up the log
Page Tracking – Application.cfc
onSessionStart() {
session.pageTracker = [];
}
onRequestStart() {
sessionManager.logPageRequest();
}
Page Tracking – SessionManager.cfc
public void function logPageRequest( numeric maxPages=40 ) {
//exclude ajax calls to remote services
if ( NOT findNoCase("/remote",cgi.script_name ) ) {
//list of controller actions to not include in pageTracker
var excludeList = “JSerror,404error";
var thisPage = cgi.script_name & '?' & cgi.query_string;
var pageAction = structKeyExists(URL,"action") ? URL.action : '';
if (NOT ListFindNoCase(excludeList, pageAction ) ) {
// skip duplicate page requests
if ( arrayLen(session.pageTracker) IS 0 OR session.pageTracker[1] IS NOT thisPage ) {
lock scope="session" type="exclusive" timeout="10" {
if ( arrayLen(session.pageTracker) LT arguments. maxPages ) {
arrayPrepend(session.pageTracker, thisPage);
} else {
arrayDeleteAt(session.pageTracker, arrayLen(session.pageTracker));
arrayPrepend(session.pageTracker, thisPage);
}
}
}
}
}
}
Action Tracking
• In some applications we may want to also track user actions that are
independent of a page load, for instance steps of a checkout that are
redrawn, not page loads.
• AngularJS applications commonly may need this kind of tracking.
• Can be combined with the page request tracking.
Action Tracking – RemoteBase.cfc
remote function updatePageTracker( string pageAction = '')
{
if (NOT len(arguments.pageAction) ) {
arguments.pageAction = cgi.QUERY_STRING;
}
sessionManager.logPageAction(arguments.pageAction);
return true;
}
Action Tracking – SessionManager.cfc
public void function logPageAction( required string pageAction, numeric maxPages=40 ) {
if ( arrayLen(session.pageTracker) IS 0
OR session.pageTracker[1] IS NOT arguments.pageAction ) {
lock scope="session" type="exclusive" timeout="10" {
if ( arrayLen(session.pageTracker) LT arguments.maxPages) {
arrayPrepend(session.pageTracker,arguments.pageAction);
} else {
arrayDeleteAt(session.pageTracker, arrayLen(session.pageTracker));
arrayPrepend(session.pageTracker, arguments.pageAction);
}
}
}
}
Action Tracking – JS Method to Log Action
function updatePageTracker (pageAction){
pagetrackerJQXHR = $.ajax({
url: '/remote/remotecustomerservice.cfc',
data: {
method: 'updatePageTracker',
pageAction: pageAction,
returnFormat: 'json'
}
}).done( function(response) {
// success
return response;
});
}
Action Tracking – JS Method to Log Action, Cont.
function startCheckout() {
updatePageTracker('Checkout: Start Checkout');
etc….
}
CF Error Logging - CFLog
• Simplest way to do basic error logging.
• Good idea to use inside cfcatch blocks if you aren’t doing anything else to
track the error you are catching and it is not something that should
routinely occur.
• Likewise you can use in your global error handler to log errors you aren’t
otherwise capturing particularly any that might not get logged via CF’s own
logging of errors.
• Doesn’t really provide much information to help you figure outWHY the
error occurred, just that it did.
CF Error Logging - LogBox
• From the team at ColdBox.
• Complete library for logging and other notification methods.
• Provides a much wider range of ways to treat items you want to log, such as
logging to a database, rolling files, sending emails or SMS notifications, etc.
• Allows for custom “appenders” which currently include ones such asTwitter
and Slack.
• Easily change the handling of logging/notifications on a per-environment
basis.
CF Error Logging - BugLogHQ
• Open sourceColdFusion application for bug logging and tracking
• Features rival some commercial bug tracking systems
• Very easy to install and get running
• Simple integration into CF applications via REST sevice
• Allows for easy overview of errors with counts on each
• Set up email rules for things like first instance of an error or for every x times
an error is logged.
• Can customize the data you send in, more than just the error captured
Advanced Error Handling Strategies for ColdFusion
Advanced Error Handling Strategies for ColdFusion
Javascript Errors
window.onerror = function (errorMsg, url, lineNumber) {
log('Error: ' + errorMsg + ' Script: ' + url + ' Line: ' + lineNumber);
}
Newer Browsers:
window.onerror = function (errorMsg, url, lineNumber, column, errorObj) {
log('Error: ' + errorMsg + ' Script: ' + url + ' Line: ' + lineNumber
+ ' Column: ' + column + ' StackTrace: ' + errorObj);
}
Javascript Errors
window.onerror = function (errorMsg, url, lineNumber) {
alert('Error: ' + errorMsg + ' Script: ' + url + ' Line: ' + lineNumber);
}
Newer Browsers:
window.onerror = function (errorMsg, url, lineNumber, column, errorObj) {
alert('Error: ' + errorMsg + ' Script: ' + url + ' Line: ' + lineNumber
+ ' Column: ' + column + ' StackTrace: ' + errorObj);
}
Javascript Errors - Sourcemaps
• Sourcemaps are a JSON-based mapping format that creates a relationship
between a minified file and its source.
• Allows developer tools and stack traces, etc. to use the original code line
information vs. minified file version.
• Typically used for both JS and CSS files.
• Supported by most popular preprocessors like Coffeescript, UglifyJS, SASS,
LESS, etc.
• Support often built into developer tools that provide processings (CodeKit
on Mac)
Javascript Errors
• https://www.stacktracejs.com/
• Mature library for capturing and outputting full stack trace of your JS errors.
• Great tool for developers that want to roll their own JS error handling.
Javascript Errors – 3rd Party Tools
• https://bugsnag.com/
• https://sentry.io
• https://trackjs.com
• https://raygun.com
• https://rollbar.com/
• Many more!
Why Use 3rd Party Tools?
• Captures lots of data automatically (browser/OS data, user IP, etc.)
• APIs for logging allow for use in different places in your application.
• Typically will capture full stack traces and handle source maps.
• Easily include additional data with your errors as part of your JSON package
• Most support a wide variety of languages and can even be used for your
ColdFusion errors as well (Raygun seems to be the only one with a CF
provider already done, but usually not hard to roll your own).
• Integration with lots of other services like SVN, Github, Slack, SMS, etc.
• Reporting and wide variety of search methods for your errors.
Why Use 3rd Party Tools? Cont.
• Automatic creation of tickets in systems like Jira, BitBucket, Lighthouse,
etc.
• Can add notes to bugs, mark as fixed, etc.
• Wide variety of notification options from email to SMS, Slack, etc.
• Bug assignment to developers, status tracking, etc.
• Daily email summaries
• Notification of noticeable increases in errors.
• Can track errors based on versions/deployments.
Advanced Error Handling Strategies for ColdFusion
Advanced Error Handling Strategies for ColdFusion
Advanced Error Handling Strategies for ColdFusion
Ajax Tracking
• What if our site makes heavy use of Ajax calls to perform much of its work?
• A lot of times if there’s an error in the Ajax call, it’s helpful to have more
information than just the error, we need to know what the data sent to the
Ajax method included, and what CF returned from it.
• Global method for logging Ajax calls, saved into session memory
• We can then dump the session var into CF global error handler as part of the
session, and into JSON package for services like BugSnag.
Ajax Tracking – RemoteBase.cfc
component name="RemoteBase" output="false" {
variables.sessionManager = Application.wirebox.getInstance("SessionManager");
function init() {
return this;
}
public void function logAjax( struct strArguments, string methodName='Unknown Method' )
{
strArguments.methodName = arguments.methodName; variables.sessionManager.logAjax(argumentCollection=strArguments);
}
public void function logAjaxResponse( string methodName='Unknown Method', any result = {} )
{
variables.sessionManager.logAjaxResponse(arguments.methodName, result);
}
}
Ajax Tracking – SessionManager.cfc
public void function logAjax(numeric maxCalls = 20){
var secureVars = 'password,confirmpw,ccNumber,cvv2';
var methodArgs = Duplicate(arguments);
var methodName = 'notLogged';
if (structKeyExists(methodArgs,"methodName"))
{
methodName = methodArgs.methodName;
structDelete(methodArgs,"methodName");
}
for (var item in secureVars)
{
if (structKeyExists(methodArgs, item))
{
methodArgs[item] = 'removed';
}
}
var ajaxData = { ajaxRequest = cgi.script_name, methodName = methodName, methodArgs = methodArgs,
requestTime = dateFormat(Now())&'-'&timeFormat(Now()) };
cont….
Ajax Tracking – SessionManager.cfc, cont.
public void function logAjax(numeric maxCalls = 20){
…
// skip duplicate ajax requests
if ( arrayLen(session.ajaxTracker) IS 0
OR session.ajaxTracker[1].ajaxRequest IS NOT ajaxData.ajaxRequest
OR session.ajaxTracker[1].methodName IS NOT ajaxData.methodName )
{
lock scope="session" type="exclusive" timeout="10"
{
if ( arrayLen(session.ajaxTracker) LT arguments. maxCalls )
{
arrayPrepend(session.ajaxTracker, ajaxData);
} else {
arrayDeleteAt(session.ajaxTracker,arrayLen(session.ajaxTracker));
arrayPrepend(session.ajaxTracker, ajaxData);
}
}
}
}
Ajax Tracking – SessionManager.cfc, cont.
public void function logAjaxResponse(methodName, result){
if ( arrayLen(session.ajaxTracker) GT 0
AND session.ajaxTracker[1].methodName IS arguments.methodName ) {
lock scope="session" type="exclusive" timeout="10" {
session.ajaxTracker[1].ajaxResponse = result;
}
}
}
Ajax Tracking – Sample Remote method
remote struct function doLogin() {
logAjax(arguments, 'CustomerService - doLogin');
var result = customerService.doLogin(argumentCollection=arguments);
logAjaxResponse('CustomerService - doLogin', result);
return result;
}
Browser Feature Detection
• When reporting bugs, it can be very useful to have a report on what features
the user’s browser supports or has disabled at the time.
• BrowserHawk – popular commercial product for browser feature detection
• Can detect everything from simple browser name and version to Flash,
window size, mobile browser, ActiveX, popups blocked, webstore enabled,
etc.
• Modernizr – customizable javascript library for detecting browser
features/support (https://modernizr.com).
BugSnag Notifier
• Include Bugsnag.js library in your header, send JSON payload to record errors
• Unhandled exceptions automatically reported
• Use notifyException() in try/catch blocks to send error notifications
• Use notify() for custom errors that don’t include an error object
• Automatically will collect user data like IP address as well as user events like mouse
clicks.
• Allows sending any additional data on the user to load on the User tab.
• Can send any addition data elements in a “metadata” object to create new tabs on
your error reports.
BugSnag Notifier – Send Notification
function doBugSnag(errorType, err, dataObj) {
$.ajax({
url: "/remote/remotecustomerservice.cfc",
data: { method: "getSessionData",
returnFormat: "json" },
success: function(response){
Bugsnag.user = { email: response.email,
pageTracker:response.pagetracker,
ajaxTracker: response.ajaxtracker };
Bugsnag.metaData = {};
//check for Ajax errors
if ( dataObj && dataObj.jqxhr ) {
var errorMessage = "Ajax Error";
//remove HTML tags
var responseText = dataObj.responseText.replace(/</?[^>]+(>|$)/g, "n"); var params = dataObj.data;
Bugsnag.metaData = { 'Ajax Request': { 'Parameters': params, 'URL': dataObj.url }, 'Ajax
Response': { 'Code': dataObj.status, 'Detail': dataObj.statusText }; Bugsnag.notifyException(err, errorMessage);
} else {
Bugsnag.notifyException(err, {groupingHash: errorType}); } }
}
)}
}
BugSnag Notifier – Ajax Error Handler
function ajaxError(objAjax, xhr, textStatus, errorThrown ) {
var thrownError = errorThrown || textStatus;
if (xhr.status != 200 ) {
thrownError = xhr.statusText;
}
var objError = new Error(thrownError);
var dataObj = { 'jqxhr': xhr,
'data': objAjax.data,
'url': objAjax.url,
'errorDetails': thrownError };
if (textStatus == 'timeout') {
doBugSnag("Ajax Timeout : " + thrownError,objError, dataObj );
return;
} else if (!window.cancelErrors && (xhr.readyState == 0 || xhr.status == 0 ) ) { doBugSnag("Ajax Aborted", objError, dataObj );
return;
} else if (xhr.status == 500) {
doBugSnag("Ajax Request Error: " + thrownError,objError, dataObj );
} else if (!window.cancelErrors){
doBugSnag("Unknown Ajax Error:" + textStatus, objError, dataObj );
return;
}
}
BugSnag Notifier – Ajax Error Handler, cont.
function sampleJS () {
$.ajax({
url: "/remote/remotecustomerservice.cfc",
data: {
method: ”loginUser",
returnFormat: "json"
},
success: function(response){
…. Stuff here
},
error : function(xhr, textStatus) {
ajaxError(this, xhr, textStatus);
}
});
}
Advanced Error Handling Strategies for ColdFusion
JSON Formatter
• The more complex the objects you send to BugSnag, the more difficult they
become to read/parse due to being displayed in simple text.
• To work around this, I implemented a JSON formatter which takes a raw
JSON object and shows it as a formatted tree object with expand/collapse
ability.
• When an error is encountered, CF writes out the JSON to a text file on the
server and then provides a link back to it, that will pre-load it into the
formatter.
• That URL is then included in the data sent to BugSnag along with the raw
JSON.
Advanced Error Handling Strategies for ColdFusion
Advanced Error Handling Strategies for ColdFusion
Analytics
• If we use Google orAdobe, etc. analytics, what else can we track to look at
customer issues on the site?
• Shopping Cart/Checkout abandonment/funnels
• 404, 500 etc. Errors
• Search Relevancy/Success Metrics
SQL Logging – History Tables
• Commonly used with triggers to copy data from a table prior to completing
an CRUD action.
• Should be used for any commonly edited data
• Useful to save date of the update, user, and type of update.
• Commonly developers will just copy the entire record, leading to lots of
bloat and making it hard to find the relevant data.
• Far more useful is to identify the columns that will change, and which of
those we even care about.
• http://database-programmer.blogspot.com/2008/07/history-tables.html
SQL Logging - Errors
• What if we have an undiagnosed error occurring in our database layer?
• Stored Procedures are commonly used in Enterprise applications and often
having internal variables that can result in errors thrown that can be
difficult, if not impossible, to debug without more information.
• Adding a SQL error log table
• Global vs. Custom Error Logging
SQL Error Logging – Global Error Log Table
CREATE TABLE [dbo].[ErrorLog] (
[ErrorId] int IDENTITY(1,1) NOT NULL,
[DateOccured] datetime NOT NULL DEFAULT GETDATE(),
[ErrorDesc] varchar(255) NULL,
[UserDisplayMsg] varchar(255) NULL,
[ObjectName] varchar(20) NULL,
[CodeLineNbr] int NULL,
[VariableData] text NULL,
[UserId] int NOT NULL
)
SQL Error Logging – Global Errors – Add Entry
INSERT INTO [dbo].[ErrorLog]
( ErrorDesc
, UserDisplayMsg
, ObjectName
, CodeLineNbr
, VariableData
, UserId )
VALUES
( ’Null value for InvoiceTotal’
, ‘There was a problem completing your order’
, ‘spWebCreateFinalInvoice’
, 203
, '@subtotal: ' + cast(@subtotal as varchar(10))
, @UserID
)
END
SQL Error Logging – Custom Error Log Table
CREATE TABLE [dbo].[InvoiceNullError] (
[InvoiceNullErrorId] int IDENTITY(1,1) NOT NULL,
[Location] varchar(50) NULL,
[InvoiceId] int NULL,
[CustomerId] int NULL,
[ShoppingCartId] int NULL,
[InvoiceGrandTotalAmt] decimal(8,2) NULL,
[InvoiceShippingCost] decimal(8,2) NULL,
[OrderShippingCost] decimal(8,2) NULL,
[InvoiceFreightCost] decimal(8,2) NULL,
[OrderFreightCost] decimal(8,2) NULL,
[TaxRate] decimal(8,4) NULL,
[DateEntered] datetime NULL DEFAULT (getdate())
)
SQL Error Logging – Custom Errors – Add Entry
BEGIN
EXEC WebCalculateTaxRate @CustomerId, @TaxRate OUT
IF @TaxRate is null
BEGIN
INSERT INTO [dbo].[InvoiceNullError]
(Location, InvoiceId, CustomerId, ShoppingCartId,
InvoiceGrandTotalAmt, InvoiceShippingCost, OrderShippingCost,
InvoiceFreightCost, OrderFreightCost, TaxRate)
VALUES
('after WebCalculateTaxRate’, @InvoiceId, @CustomerId,
@ShoppingCartId, @InvoiceGrandTotalAmt, @InvoiceShippingCost, @OrderShippingCost,
@InvoiceFreightCost, @OrderFreightCost, @TaxRate)
END
END
Application Performance Management
• APM is all about understanding the “why” as fast as possible
• The heart of APM solutions is understanding why transactions in your
application are slow or failing.
• Be sure to look forAPM tools that go to code level profiling.
Application Performance Management
• Full Stack Monitoring and Reporting on yourWebsite
• Detects Problems in Database,Application andWeb Servers, Client Side, etc. layers
• Metrics such as CPU, RAM usage, speed, etc. for processes reported separately.
• Typically can detect all the application dependencies automatically and report on
them
• Can see specific customers experiencing issues and drill right down to where the
error(s) are occurring.
• Can search on specific user metrics as well to find the entire trace of a user’s
activity on the site to debug a problem they report to customer service.
Application Performance Management, Cont.
• Typically these products include a wide range of additional capabilities,
automation, reporting tools, plugins, etc.
• Examples: integration into build processes and testing, for example to make
sure each test meets specific performance metrics.
• Automatic determination of root cause of issues.
• Monitor server performance, virtual machines, etc.
• Integrates with any number of notification methods: HipChat, Slack,
Webhooks to do any kind of custom notification, etc.
Popular APM Platforms
• Dynatrace - https://www.dynatrace.com
• Stackify Retrace - https://stackify.com/retrace/
• CiscoAppDynamics - https://www.appdynamics.com
• New Relic APM - https://newrelic.com/application-monitoring
Dynatrace – Site Analytics with User Experience
Dynatrace – Javascript Error Report
Dynatrace – Problem Evolution
Dynatrace – Problem Root Cause Report
Dynatrace – Database Report
Dynatrace – Database Method Report
Dynatrace – Unit Test/Eclipse Plugin
Dynatrace – Source Code Lookup in Eclipse
Dynatrace – Mobile Apps
Questions?

More Related Content

Advanced Error Handling Strategies for ColdFusion

  • 1. Advanced Error Handling Strategies By Mary Jo Sminkey Senior Web Developer CFWebtools, LLC
  • 2. Who Am I? • Senior Web Developer at CFWebtools, LLC • ColdFusion Developer since CF3/Allaire • Cosplayer (esp. Dr Who and Star Trek) • Dog Trainer and Instructor • Sewer/Baker/Knitter/Origamist/Ass. Crafts • Cancer Survivor • Fibromyalgia/Invisible Disabilities Advocate
  • 3. No Site Is Going to Be Bug-Free
  • 4. Do You Get Emails Like This? “What a pathetic useless website you have. Even when I used the exact same words you use in your product description, all I got was sorry no results for your search.” “your on line web site sucks ” “this stupid website will not allow me to purchase a part I need, I go to check out and thats as far as I can go.Thanks.” “Fix your order web site so I can place my order or I'm moving on ” “The forms and fields are virtually unreadable...the no contrast type against a white background...it really sucks.Whats up with you guys? “
  • 5. Or Maybe Ones Like This? “Your website has got to be the worst website I have been to. It is so unuser friendly. The search is useless.Very rarely do I find what I want on this website. Easier to go to the book then find the part number. I keep coming back to it with hopes then quickly get mad at how it just plain sucks! Sorry but it does.” “Hi for the last few weeks every time I try to check out and pay for my order by clicking on Billing Summary it just seems to keep loading but never lets me get to the payment stage.. any help or advice would be appreciated as ive been trying to check out a fairly large order for a few months now and not having any luckThankyou” “Lost 45 minutes trying to check out my order. Could not proceed past payment page for at least 45 minutes today. Using safari. I had to call C.S. for help but then it finally just went through.”
  • 6. Or My Personal Favorite! “Hire a new IT leader !Your site sucksASS. Could not make a order with out a password reset. No F, way. Call during business hours. Really ? How much $$ are you loosing because of your IT dumb ass ?Think about it. Hope these emails get past you to some one with a stake in the company. ” Yes – these really are actual emails that have come into the webmaster account for one of the sites I have worked with. And no – I’m really not bad at my job! 🌞
  • 7. Do These Just Go Into Your Trash Folder? • It’s tempting to just trash complaints particularly when they offer little detail about the problem (and especially when they often come across as a personal attack on your skills). • But regardless of the tone of the complaints you get, you have to assume that there are many other customers probably experiencing the same issues but that didn’t bother to email you, they just left for another site where they aren’t having a problem. • A developer that has the best interest of their client in mind needs to find ways to figure out the reason these users are unhappy and SOLVE those problems.
  • 8. Other Advantages to Good Error Tracking • Many errors don’t get reported at all. • If you keep a good eye on the errors that come through, you can often push out quick fixes before your client even reports them to you. Big bonus points with clients. • If your error handling captures end user data, you can do the same thing, fix the error the user encountered and then email them to let them know it’s been fixed. Huge bonus points again with end users that you are on top of the issues this fast. • Often error reports can give you heads-up on hack attempts coming into the site that you might need to block.
  • 9. How Do We Get More Information from the User? • If you’ve ever tried replying to an email like these to get more information from the customer, you know how fruitless that often is. • Customers always assume that the issue they are having is happening for everyone, developers know thatVERY often isn’t true. • Many problems are due to customers doing things we don’t even expect, like misspelling words in their search, and not figuring that out when the search doesn’t work. • Many times a request for more information just results in more angry replies questioning your competence and your mother’s parentage, and most users have no clue about things like the error console in their browser. • We need to get information from the user *without* their help.
  • 10. Ways to Collect User Data When Problems Occur • Global Error Handler is a bare minimum (cferror template) • Track User Actions in Session • Use BugTracking Systems • Log Errors (cflog, SQL error logging)
  • 11. Global Error Handler • Cferror vs. using an OnError • Allows you to dump all the available scopes • Able to include into a cfcatch block • Use Application variable to track number of instances of errors (prevent emailing lots of copies of same error) • Additional emails when we detect large number of errors occurring • Clean the scopes of sensitive data elements (or very large data structures we don’t need). • Logging of errors we don’t want to email (db timeouts, etc.)
  • 12. Global Error Handler – More Info • Blog Articles at ColdFusionMuse • http://www.coldfusionmuse.com/index.cfm/2014/2/11/Building-a-Robust-Error-Handler • http://www.coldfusionmuse.com/index.cfm/2017/7/12/robust-error-handling-part2 • NCDevCon 2015 Presentation • https://www.youtube.com/watch?v=c0dQfBoCCmo
  • 13. Page Tracking • First thing we want to know when a user has problems…what URLs have they clicked through before reporting it • SessionTracker for page clicks • Run on RequestStart so each page logged automatically • Easily modify number of pages to include, etc. • Clear out old pages as new pages added to tracker • Excluding Pages that clog up the log
  • 14. Page Tracking – Application.cfc onSessionStart() { session.pageTracker = []; } onRequestStart() { sessionManager.logPageRequest(); }
  • 15. Page Tracking – SessionManager.cfc public void function logPageRequest( numeric maxPages=40 ) { //exclude ajax calls to remote services if ( NOT findNoCase("/remote",cgi.script_name ) ) { //list of controller actions to not include in pageTracker var excludeList = “JSerror,404error"; var thisPage = cgi.script_name & '?' & cgi.query_string; var pageAction = structKeyExists(URL,"action") ? URL.action : ''; if (NOT ListFindNoCase(excludeList, pageAction ) ) { // skip duplicate page requests if ( arrayLen(session.pageTracker) IS 0 OR session.pageTracker[1] IS NOT thisPage ) { lock scope="session" type="exclusive" timeout="10" { if ( arrayLen(session.pageTracker) LT arguments. maxPages ) { arrayPrepend(session.pageTracker, thisPage); } else { arrayDeleteAt(session.pageTracker, arrayLen(session.pageTracker)); arrayPrepend(session.pageTracker, thisPage); } } } } } }
  • 16. Action Tracking • In some applications we may want to also track user actions that are independent of a page load, for instance steps of a checkout that are redrawn, not page loads. • AngularJS applications commonly may need this kind of tracking. • Can be combined with the page request tracking.
  • 17. Action Tracking – RemoteBase.cfc remote function updatePageTracker( string pageAction = '') { if (NOT len(arguments.pageAction) ) { arguments.pageAction = cgi.QUERY_STRING; } sessionManager.logPageAction(arguments.pageAction); return true; }
  • 18. Action Tracking – SessionManager.cfc public void function logPageAction( required string pageAction, numeric maxPages=40 ) { if ( arrayLen(session.pageTracker) IS 0 OR session.pageTracker[1] IS NOT arguments.pageAction ) { lock scope="session" type="exclusive" timeout="10" { if ( arrayLen(session.pageTracker) LT arguments.maxPages) { arrayPrepend(session.pageTracker,arguments.pageAction); } else { arrayDeleteAt(session.pageTracker, arrayLen(session.pageTracker)); arrayPrepend(session.pageTracker, arguments.pageAction); } } } }
  • 19. Action Tracking – JS Method to Log Action function updatePageTracker (pageAction){ pagetrackerJQXHR = $.ajax({ url: '/remote/remotecustomerservice.cfc', data: { method: 'updatePageTracker', pageAction: pageAction, returnFormat: 'json' } }).done( function(response) { // success return response; }); }
  • 20. Action Tracking – JS Method to Log Action, Cont. function startCheckout() { updatePageTracker('Checkout: Start Checkout'); etc…. }
  • 21. CF Error Logging - CFLog • Simplest way to do basic error logging. • Good idea to use inside cfcatch blocks if you aren’t doing anything else to track the error you are catching and it is not something that should routinely occur. • Likewise you can use in your global error handler to log errors you aren’t otherwise capturing particularly any that might not get logged via CF’s own logging of errors. • Doesn’t really provide much information to help you figure outWHY the error occurred, just that it did.
  • 22. CF Error Logging - LogBox • From the team at ColdBox. • Complete library for logging and other notification methods. • Provides a much wider range of ways to treat items you want to log, such as logging to a database, rolling files, sending emails or SMS notifications, etc. • Allows for custom “appenders” which currently include ones such asTwitter and Slack. • Easily change the handling of logging/notifications on a per-environment basis.
  • 23. CF Error Logging - BugLogHQ • Open sourceColdFusion application for bug logging and tracking • Features rival some commercial bug tracking systems • Very easy to install and get running • Simple integration into CF applications via REST sevice • Allows for easy overview of errors with counts on each • Set up email rules for things like first instance of an error or for every x times an error is logged. • Can customize the data you send in, more than just the error captured
  • 26. Javascript Errors window.onerror = function (errorMsg, url, lineNumber) { log('Error: ' + errorMsg + ' Script: ' + url + ' Line: ' + lineNumber); } Newer Browsers: window.onerror = function (errorMsg, url, lineNumber, column, errorObj) { log('Error: ' + errorMsg + ' Script: ' + url + ' Line: ' + lineNumber + ' Column: ' + column + ' StackTrace: ' + errorObj); }
  • 27. Javascript Errors window.onerror = function (errorMsg, url, lineNumber) { alert('Error: ' + errorMsg + ' Script: ' + url + ' Line: ' + lineNumber); } Newer Browsers: window.onerror = function (errorMsg, url, lineNumber, column, errorObj) { alert('Error: ' + errorMsg + ' Script: ' + url + ' Line: ' + lineNumber + ' Column: ' + column + ' StackTrace: ' + errorObj); }
  • 28. Javascript Errors - Sourcemaps • Sourcemaps are a JSON-based mapping format that creates a relationship between a minified file and its source. • Allows developer tools and stack traces, etc. to use the original code line information vs. minified file version. • Typically used for both JS and CSS files. • Supported by most popular preprocessors like Coffeescript, UglifyJS, SASS, LESS, etc. • Support often built into developer tools that provide processings (CodeKit on Mac)
  • 29. Javascript Errors • https://www.stacktracejs.com/ • Mature library for capturing and outputting full stack trace of your JS errors. • Great tool for developers that want to roll their own JS error handling.
  • 30. Javascript Errors – 3rd Party Tools • https://bugsnag.com/ • https://sentry.io • https://trackjs.com • https://raygun.com • https://rollbar.com/ • Many more!
  • 31. Why Use 3rd Party Tools? • Captures lots of data automatically (browser/OS data, user IP, etc.) • APIs for logging allow for use in different places in your application. • Typically will capture full stack traces and handle source maps. • Easily include additional data with your errors as part of your JSON package • Most support a wide variety of languages and can even be used for your ColdFusion errors as well (Raygun seems to be the only one with a CF provider already done, but usually not hard to roll your own). • Integration with lots of other services like SVN, Github, Slack, SMS, etc. • Reporting and wide variety of search methods for your errors.
  • 32. Why Use 3rd Party Tools? Cont. • Automatic creation of tickets in systems like Jira, BitBucket, Lighthouse, etc. • Can add notes to bugs, mark as fixed, etc. • Wide variety of notification options from email to SMS, Slack, etc. • Bug assignment to developers, status tracking, etc. • Daily email summaries • Notification of noticeable increases in errors. • Can track errors based on versions/deployments.
  • 36. Ajax Tracking • What if our site makes heavy use of Ajax calls to perform much of its work? • A lot of times if there’s an error in the Ajax call, it’s helpful to have more information than just the error, we need to know what the data sent to the Ajax method included, and what CF returned from it. • Global method for logging Ajax calls, saved into session memory • We can then dump the session var into CF global error handler as part of the session, and into JSON package for services like BugSnag.
  • 37. Ajax Tracking – RemoteBase.cfc component name="RemoteBase" output="false" { variables.sessionManager = Application.wirebox.getInstance("SessionManager"); function init() { return this; } public void function logAjax( struct strArguments, string methodName='Unknown Method' ) { strArguments.methodName = arguments.methodName; variables.sessionManager.logAjax(argumentCollection=strArguments); } public void function logAjaxResponse( string methodName='Unknown Method', any result = {} ) { variables.sessionManager.logAjaxResponse(arguments.methodName, result); } }
  • 38. Ajax Tracking – SessionManager.cfc public void function logAjax(numeric maxCalls = 20){ var secureVars = 'password,confirmpw,ccNumber,cvv2'; var methodArgs = Duplicate(arguments); var methodName = 'notLogged'; if (structKeyExists(methodArgs,"methodName")) { methodName = methodArgs.methodName; structDelete(methodArgs,"methodName"); } for (var item in secureVars) { if (structKeyExists(methodArgs, item)) { methodArgs[item] = 'removed'; } } var ajaxData = { ajaxRequest = cgi.script_name, methodName = methodName, methodArgs = methodArgs, requestTime = dateFormat(Now())&'-'&timeFormat(Now()) }; cont….
  • 39. Ajax Tracking – SessionManager.cfc, cont. public void function logAjax(numeric maxCalls = 20){ … // skip duplicate ajax requests if ( arrayLen(session.ajaxTracker) IS 0 OR session.ajaxTracker[1].ajaxRequest IS NOT ajaxData.ajaxRequest OR session.ajaxTracker[1].methodName IS NOT ajaxData.methodName ) { lock scope="session" type="exclusive" timeout="10" { if ( arrayLen(session.ajaxTracker) LT arguments. maxCalls ) { arrayPrepend(session.ajaxTracker, ajaxData); } else { arrayDeleteAt(session.ajaxTracker,arrayLen(session.ajaxTracker)); arrayPrepend(session.ajaxTracker, ajaxData); } } } }
  • 40. Ajax Tracking – SessionManager.cfc, cont. public void function logAjaxResponse(methodName, result){ if ( arrayLen(session.ajaxTracker) GT 0 AND session.ajaxTracker[1].methodName IS arguments.methodName ) { lock scope="session" type="exclusive" timeout="10" { session.ajaxTracker[1].ajaxResponse = result; } } }
  • 41. Ajax Tracking – Sample Remote method remote struct function doLogin() { logAjax(arguments, 'CustomerService - doLogin'); var result = customerService.doLogin(argumentCollection=arguments); logAjaxResponse('CustomerService - doLogin', result); return result; }
  • 42. Browser Feature Detection • When reporting bugs, it can be very useful to have a report on what features the user’s browser supports or has disabled at the time. • BrowserHawk – popular commercial product for browser feature detection • Can detect everything from simple browser name and version to Flash, window size, mobile browser, ActiveX, popups blocked, webstore enabled, etc. • Modernizr – customizable javascript library for detecting browser features/support (https://modernizr.com).
  • 43. BugSnag Notifier • Include Bugsnag.js library in your header, send JSON payload to record errors • Unhandled exceptions automatically reported • Use notifyException() in try/catch blocks to send error notifications • Use notify() for custom errors that don’t include an error object • Automatically will collect user data like IP address as well as user events like mouse clicks. • Allows sending any additional data on the user to load on the User tab. • Can send any addition data elements in a “metadata” object to create new tabs on your error reports.
  • 44. BugSnag Notifier – Send Notification function doBugSnag(errorType, err, dataObj) { $.ajax({ url: "/remote/remotecustomerservice.cfc", data: { method: "getSessionData", returnFormat: "json" }, success: function(response){ Bugsnag.user = { email: response.email, pageTracker:response.pagetracker, ajaxTracker: response.ajaxtracker }; Bugsnag.metaData = {}; //check for Ajax errors if ( dataObj && dataObj.jqxhr ) { var errorMessage = "Ajax Error"; //remove HTML tags var responseText = dataObj.responseText.replace(/</?[^>]+(>|$)/g, "n"); var params = dataObj.data; Bugsnag.metaData = { 'Ajax Request': { 'Parameters': params, 'URL': dataObj.url }, 'Ajax Response': { 'Code': dataObj.status, 'Detail': dataObj.statusText }; Bugsnag.notifyException(err, errorMessage); } else { Bugsnag.notifyException(err, {groupingHash: errorType}); } } } )} }
  • 45. BugSnag Notifier – Ajax Error Handler function ajaxError(objAjax, xhr, textStatus, errorThrown ) { var thrownError = errorThrown || textStatus; if (xhr.status != 200 ) { thrownError = xhr.statusText; } var objError = new Error(thrownError); var dataObj = { 'jqxhr': xhr, 'data': objAjax.data, 'url': objAjax.url, 'errorDetails': thrownError }; if (textStatus == 'timeout') { doBugSnag("Ajax Timeout : " + thrownError,objError, dataObj ); return; } else if (!window.cancelErrors && (xhr.readyState == 0 || xhr.status == 0 ) ) { doBugSnag("Ajax Aborted", objError, dataObj ); return; } else if (xhr.status == 500) { doBugSnag("Ajax Request Error: " + thrownError,objError, dataObj ); } else if (!window.cancelErrors){ doBugSnag("Unknown Ajax Error:" + textStatus, objError, dataObj ); return; } }
  • 46. BugSnag Notifier – Ajax Error Handler, cont. function sampleJS () { $.ajax({ url: "/remote/remotecustomerservice.cfc", data: { method: ”loginUser", returnFormat: "json" }, success: function(response){ …. Stuff here }, error : function(xhr, textStatus) { ajaxError(this, xhr, textStatus); } }); }
  • 48. JSON Formatter • The more complex the objects you send to BugSnag, the more difficult they become to read/parse due to being displayed in simple text. • To work around this, I implemented a JSON formatter which takes a raw JSON object and shows it as a formatted tree object with expand/collapse ability. • When an error is encountered, CF writes out the JSON to a text file on the server and then provides a link back to it, that will pre-load it into the formatter. • That URL is then included in the data sent to BugSnag along with the raw JSON.
  • 51. Analytics • If we use Google orAdobe, etc. analytics, what else can we track to look at customer issues on the site? • Shopping Cart/Checkout abandonment/funnels • 404, 500 etc. Errors • Search Relevancy/Success Metrics
  • 52. SQL Logging – History Tables • Commonly used with triggers to copy data from a table prior to completing an CRUD action. • Should be used for any commonly edited data • Useful to save date of the update, user, and type of update. • Commonly developers will just copy the entire record, leading to lots of bloat and making it hard to find the relevant data. • Far more useful is to identify the columns that will change, and which of those we even care about. • http://database-programmer.blogspot.com/2008/07/history-tables.html
  • 53. SQL Logging - Errors • What if we have an undiagnosed error occurring in our database layer? • Stored Procedures are commonly used in Enterprise applications and often having internal variables that can result in errors thrown that can be difficult, if not impossible, to debug without more information. • Adding a SQL error log table • Global vs. Custom Error Logging
  • 54. SQL Error Logging – Global Error Log Table CREATE TABLE [dbo].[ErrorLog] ( [ErrorId] int IDENTITY(1,1) NOT NULL, [DateOccured] datetime NOT NULL DEFAULT GETDATE(), [ErrorDesc] varchar(255) NULL, [UserDisplayMsg] varchar(255) NULL, [ObjectName] varchar(20) NULL, [CodeLineNbr] int NULL, [VariableData] text NULL, [UserId] int NOT NULL )
  • 55. SQL Error Logging – Global Errors – Add Entry INSERT INTO [dbo].[ErrorLog] ( ErrorDesc , UserDisplayMsg , ObjectName , CodeLineNbr , VariableData , UserId ) VALUES ( ’Null value for InvoiceTotal’ , ‘There was a problem completing your order’ , ‘spWebCreateFinalInvoice’ , 203 , '@subtotal: ' + cast(@subtotal as varchar(10)) , @UserID ) END
  • 56. SQL Error Logging – Custom Error Log Table CREATE TABLE [dbo].[InvoiceNullError] ( [InvoiceNullErrorId] int IDENTITY(1,1) NOT NULL, [Location] varchar(50) NULL, [InvoiceId] int NULL, [CustomerId] int NULL, [ShoppingCartId] int NULL, [InvoiceGrandTotalAmt] decimal(8,2) NULL, [InvoiceShippingCost] decimal(8,2) NULL, [OrderShippingCost] decimal(8,2) NULL, [InvoiceFreightCost] decimal(8,2) NULL, [OrderFreightCost] decimal(8,2) NULL, [TaxRate] decimal(8,4) NULL, [DateEntered] datetime NULL DEFAULT (getdate()) )
  • 57. SQL Error Logging – Custom Errors – Add Entry BEGIN EXEC WebCalculateTaxRate @CustomerId, @TaxRate OUT IF @TaxRate is null BEGIN INSERT INTO [dbo].[InvoiceNullError] (Location, InvoiceId, CustomerId, ShoppingCartId, InvoiceGrandTotalAmt, InvoiceShippingCost, OrderShippingCost, InvoiceFreightCost, OrderFreightCost, TaxRate) VALUES ('after WebCalculateTaxRate’, @InvoiceId, @CustomerId, @ShoppingCartId, @InvoiceGrandTotalAmt, @InvoiceShippingCost, @OrderShippingCost, @InvoiceFreightCost, @OrderFreightCost, @TaxRate) END END
  • 58. Application Performance Management • APM is all about understanding the “why” as fast as possible • The heart of APM solutions is understanding why transactions in your application are slow or failing. • Be sure to look forAPM tools that go to code level profiling.
  • 59. Application Performance Management • Full Stack Monitoring and Reporting on yourWebsite • Detects Problems in Database,Application andWeb Servers, Client Side, etc. layers • Metrics such as CPU, RAM usage, speed, etc. for processes reported separately. • Typically can detect all the application dependencies automatically and report on them • Can see specific customers experiencing issues and drill right down to where the error(s) are occurring. • Can search on specific user metrics as well to find the entire trace of a user’s activity on the site to debug a problem they report to customer service.
  • 60. Application Performance Management, Cont. • Typically these products include a wide range of additional capabilities, automation, reporting tools, plugins, etc. • Examples: integration into build processes and testing, for example to make sure each test meets specific performance metrics. • Automatic determination of root cause of issues. • Monitor server performance, virtual machines, etc. • Integrates with any number of notification methods: HipChat, Slack, Webhooks to do any kind of custom notification, etc.
  • 61. Popular APM Platforms • Dynatrace - https://www.dynatrace.com • Stackify Retrace - https://stackify.com/retrace/ • CiscoAppDynamics - https://www.appdynamics.com • New Relic APM - https://newrelic.com/application-monitoring
  • 62. Dynatrace – Site Analytics with User Experience
  • 63. Dynatrace – Javascript Error Report
  • 65. Dynatrace – Problem Root Cause Report
  • 67. Dynatrace – Database Method Report
  • 68. Dynatrace – Unit Test/Eclipse Plugin
  • 69. Dynatrace – Source Code Lookup in Eclipse