AngularJS - Logging Client Side Stack Trace

Nothing beats mythical errors that happens on the client side, but not on your development machine.

This post follows yesterday's post on configuring AngularJS to catch and log exceptions back to the server via an AJAX request.

Logging the Exception

Quick recap of the logging error handler.

function error(message, data, title) {
  var $http = angular.injector(['ng']).get('$http');

  data['url'] = document.location.href;

  var request = {
    method : 'POST',
    url: _spPageContextInfo.webServerRelativeUrl + '/_layouts/MyService.svc/LogMessage',
    data: {
      'log': message,
      'meta': angular.toJson(data),
      'evt': 'error'
    },
    dataType: "json",
    headers: {
      "Content-Type": "application/json; charset=utf-8"
    }
  };
  var promise = $http(request);            

  promise['catch'](function(){
    // send to console log if can't log to webservice
    $log.error('Error: ' + message, data);
  });
}
{
  "exception":
  {
    "message":"[NG-Modular Error] Unable to get property 'Client' of undefined or null reference",
    "description":"Unable to get property 'Client' of undefined or null reference",
    "number":-2146823281
  },
  "url":"http://server/_layouts/MY/form.aspx?IsDlg=1&id=3#/client"
}

Yay it's logging.  But actually, it's pretty hard to figure out where the error is coming from.  "What we need", says the developer to the tester, "is the stack trace.  If we have the stack trace we'd be able to repo this and fix it."

stacktraceJS

Grab: https://github.com/stacktracejs/stacktrace.js/blob/stable/stacktrace.js

function error(message, data, title) {
  var $http = angular.injector(['ng']).get('$http');
  
  // work out the stacktrace
  var stack = printStackTrace({e: data, guess: true});
  data['stack'] = stack;
  // attach to the logged data

  data['url'] = document.location.href;

  var request = {
    //...
  };
  var promise = $http(request);            
}

The following JSON gets logged.

{
  "exception":
  {
    "message":"[NG-Modular Error] Unable to get property 'exist' of undefined or null reference",
    "description":"Unable to get property 'exist' of undefined or null reference",
    "number":-2146823281
  },
  "url":"http://server/_layouts/MY/form.aspx?IsDlg=1&id=138#/",
  "stack":
  [
    "{anonymous}(#object,\"other\")",
    "printStackTrace(#object)",
    "error(\"[NG-Modular Error] Unable to get property 'exist' of undefined or null reference\",#object)",
    "{anonymous}(?)",
    "{anonymous}(#function)",
    "{anonymous}(#object)",
    "{anonymous}(#object)",
    "{anonymous}(?)"
  ]
} 

Tweaks

"Those #function and #object, they look pretty stupid." commented the developer, who now has the stacktrace, but still thinks it doesn't help all that much.

A few hacks to stacktrace.js

if (arg.constructor === Object) {
  //result[i] = '#object';
  result[i] = arg.constructor.toString();
}
...
else {
   //result[i] = '?';
   result[i] = arg.constructor.toString();
}

This gives more readable stacktrace:

{
  "exception":
  {
    "message":"[NG-Modular Error] Unable to get property 'exist' of undefined or null reference",
    "description":"Unable to get property 'exist' of undefined or null reference",
    "number":-2146823281
  },
  "stack":
  [
    "{anonymous}(\nfunction Object() {\n    [native code]\n}\n,\"other\")",
    "printStackTrace(\nfunction Object() {\n    [native code]\n}\n)",
    "error(\"[NG-Modular Error] Unable to get property 'exist' of undefined or null reference\",\nfunction Object() {\n    [native code]\n}\n)",
    "{anonymous}(TypeError)",
    "{anonymous}(#function)",
    "{anonymous}(\nfunction Object() {\n    [native code]\n}\n)",
    "{anonymous}(\nfunction Object() {\n    [native code]\n}\n)",
    "{anonymous}([object Event])"
  ],
  "url":"http://server/_layouts/MY/form.aspx?IsDlg=1&id=138#/"
}

 

Future of stacktraceJS

The latest version of StacktraceJS (as of August 2015) is marching towards a full promise/A pattern where you request the stacktrace, and it returns you a promise.  Then promise resolves to an array of stack frames, which you can then pretty-print to your own liking.

var callback = function(stackframes) {
    var stringifiedStack = stackframes.map(function(sf) { 
        return sf.toString(); 
    }).join('\n'); 
    console.log(stringifiedStack); 
};
var errback = function(err) { console.log(err.message); };
StackTrace.get().then(callback, errback)

Expect the syntax to change, soon.

I'm also assuming for modern browsers, it'll even look into requesting SourceMap files for minified versions of JavaScript/TypeScript and resolve those lines in the stacktrace produced.   Currently though, it doesn't do all those things.  What it does manage so far, cross browser, is already fairly impressive and a great starting point.