Error Handling in AngularJS using JSNLog

Here we will discuss how to do error handling in AngularJS using JSNLog. How we can use JSNLog in AngularJS for error handling.

Introduction to JSNLog

Debugging client-side errors is not easy when you don’t know what a user is doing and seeing. In addition, when the errors happen, even customers don’t see them. All they see is that the application is either showing something strange or nothing at all. AngularJS handles these errors very well; it will catch client-side errors and show them in the browser console allowing your application to continue. The problem is that even a user will not know that an error happened and the details of the error unless the user is an advanced one who knows what a browser console is and how to open it.

What is the solution? (JSNLog)

Log the errors, user actions, and context information like a page name somewhere on the server side where you can easily see and review them. The goal of this article is to show you how to do it in AngularJS application using one of the multiple client-side logging libraries—JSNLog, written by Matt Perdeck. You can find a few articles by Matt explaining what JSNLog is and how to use it. I suggest you read his article first; they are very informative and have a lot of details on how to use JSNLog and how to integrate it with the server-side logging framework like NLog, Log4Net or Elmah.

Another problem which this article will help with is when you have your own custom server-side logging solution and don’t want to switch to some third party libraries such as those mentioned above.

How to use JSNLog in AngularJS?

For demo purposes, I wrote a small application which will download articles from CodeProject.com using the provided API.

Code Ref:
First, go to http://js.jsnlog.com, download a standalone jsnlog.min.js version and add it to our project.

Second, navigate to http://js.jsnlog.com/Documentation/GetStartedLogging/AngularJsErrorHandling and copy all code from this page into a separate file.

This is what the code will look like:

(function () {
‘use strict’

// Create new module logToServer with new $log service
angular.module(‘logToServer’, [])
// Make AngularJS do JavaScript logging through JSNLog so we can log to the server
// by replacing the $log service
.service(‘$log’, function () {
this.log = function (msg) {
JL(‘Angular’).trace(msg);
}
this.debug = function (msg) {
JL(‘Angular’).debug(msg);
}
this.info = function (msg) {
JL(‘Angular’).info(msg);
}
this.warn = function (msg) {
JL(‘Angular’).warn(msg);
}
this.error = function (msg) {
JL(‘Angular’).error(msg);
}
})
// Replace the factory that creates the standard $exceptionHandler service
.factory(‘$exceptionHandler’, function () {
return function (exception, cause) {
JL(‘Angular’).fatalException(cause, exception);
throw exception;
};
})
// Add a factory to create the interceptor to the logToServer module
.factory(‘logToServerInterceptor’, [‘$q’, function ($q) {
var myInterceptor = {
‘request’: function (config) {
config.msBeforeAjaxCall = new Date().getTime();
return config;
},
‘response’: function (response) {
if (response.config.warningAfter) {
var msAfterAjaxCall = new Date().getTime();
var timeTakenInMs = msAfterAjaxCall – response.config.msBeforeAjaxCall;
if (timeTakenInMs > response.config.warningAfter) {
JL(‘Angular.Ajax’).warn({
timeTakenInMs: timeTakenInMs,
config: response.config,
data: response.data
});
}
}
return response;
},
‘responseError’: function (rejection) {
var errorMessage = “timeout”;
if (rejection.status != 0) {
errorMessage = rejection.data.ExceptionMessage;
}
JL(‘Angular.Ajax’).fatalException({
errorMessage: errorMessage,
status: rejection.status,
config: rejection.config
}, rejection.data);
return $q.reject(rejection);
}
};
return myInterceptor;
}]);
})();

Code description:

Then, add (import) the logToServer module to your main module and add the new interceptor to the interceptor pipeline of the main module:

// Create module
angular.module(‘ArticlesModule’, [‘logToServer’])
// Register the controller
.controller(‘ArticlesController’, [‘$scope’, ‘$http’, ArticlesController])
// Add the new interceptor to the interceptor pipeline of the main module
.config([‘$httpProvider’, function ($httpProvider) {
$httpProvider.interceptors.push(‘logToServerInterceptor’);
}]);

And as the final step, we need to tell JSNLog what default URL to send messages to. In reality, it’s probably better to use a RESTful web service but in the demo application, a plain .aspx page will do just fine. I added the below code to the logToServer module itself so the same URL will be used for all application pages:

JL.setOptions({
‘defaultAjaxUrl’: ‘LogDetails.aspx’
})

In the server side code, be it an .aspx page or a web service, you’d use your own logging code to save the errors. I’ll just save the data to the text file for simplicity.

That’s it! You are all set to log client-side errors.

Let’s test our code. I added ‘use strict’ so all variables have to be defined, otherwise an exception will be thrown. I added myVariable to the application module and this is what was logged:

{“r”:””,”lg”:[{“l”:6000,
“m”:”{\”stack\”:\”ReferenceError: myVariable is not defined\\n at new ArticlesController
(http://localhost:4283/app.js:32:9)\\n at Object.e [as invoke]
(https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:36:365)\\n at F.instance
(https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:75:91)\\n at
https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:58:287\\n at s
(https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:7:408)\\n at G
(https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:58:270)\\n at g
(https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:51:172)\\n at g
(https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:51:189)\\n at
https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:50:280\\n at
https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:18:8\”,
\”message\”:\”myVariable is not
defined\”,\”name\”:\”ReferenceError\”}”,”n”:”Angular”,”t”:1421533414109}]}

This is useful but let’s try to improve it. One missing thing is that we don’t know what page logged the error. We can use a so called request id—see “r”:”” above. The request id is used to uniquely identify each request and it’s set to a random number in .NET edition of JSNLog. In my opinion, a random number to identify which log messages belong to which user may not be a solution for some sites, because it does not tell you anything about the user. Sometimes, it’s important to know a type of the user – regular or admin/superuser, etc. We can put any arbitrary information in the request id field but I’ll store a page name:

JL.setOptions({
‘requestId’: window.location.pathname.split(“/”).pop()
});

Now the request id has the page name:”r”:”index.htm”. The rest is the same.

What if we don’t want to log the whole exception? Then, we can change the line below:

JL(‘Angular’).fatalException(cause, exception);
to:
JL().log(4000, { ‘stack’: exception.stack, ‘error’: exception.message });
And the following will be logged:
{“r”:”Index.html”,”lg”:[{“l”:4000,
“m”:”{\”stack\”:\”ReferenceError: myVariable is not defined\\n at new
ArticlesController (http://localhost:4283/app.js:32:9)\\n at Object.e [as invoke]
(https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:36:365)\\n at F.instance
(https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:75:91)\\n at
https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:58:287\\n at s
(https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:7:408)\\n at G
(https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:58:270)\\n at g
(https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:51:172)\\n at g
(https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:51:189)\\n at
https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:50:280\\n at
https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js:18:8\”,
\”error\”:\”myVariable is not
defined\”}”,”n”:””,”t”:1421539948122}]}

Another area where we can improve is AJAX calls. In modern JavaScript applications, AJAX calls are used a lot and it’s important to log AJAX errors and their durations. Let’s change the interceptor to be like this:

.factory(‘logToServerInterceptor’, [‘$q’, function ($q) {
var myInterceptor = {
// The request function is called before the AJAX request is sent
‘request’: function (config) {
config.msBeforeAjaxCall = new Date().getTime();
return config;
},
// The response function is called after receiving a good response from the server
‘response’: function (response) {
var msAfterAjaxCall = new Date().getTime();
var timeTakenInMs = msAfterAjaxCall – response.config.msBeforeAjaxCall;
JL(‘Angular.Ajax’).info({
url: response.config.url,
timeTakenInMs: timeTakenInMs
});
return response;
},
// The responseError function is called when an error response was received,
// or when a timeout happened.
‘responseError’: function (rejection) {
var errorMessage = “unknown”;
JL(‘Angular.Ajax’).fatalException({
status: rejection.status,
url: rejection.config.url,
errorMessage: rejection.data.error
});
return $q.reject(rejection);
}
};
return myInterceptor;
}]);

Now if the AJAX call was successful, the following will be logged:

{“r”:”index.htm”,”lg”:[{“l”:3000,”m”:”{\”url\”:\”https://api.codeproject.com/v1/Articles?page=1
\”,\”timeTakenInMs\”:606}”,”n”:”Angular.Ajax”,”t”:1421543626142}]}

And if the AJAX call failed:

{“r”:”index.htm”,”lg”:
[{“l”:6000,”m”:”{\”status\”:400,\”url\”:\”https://api.codeproject.com/token\”,
\”errorMessage\”:\”invalid_client\”}”,”n”:”Angular.Ajax”,”t”:1421543588139}]}

You may be wondering how you can log something such as user actions, add some trace information, etc. This is easy.
You can add logging code anywhere. The page load time can be logged like this:

if (!window.performance) {
// IE 8 and below is not supported
JL().warn(‘Performance object is not supported’);
} else {
var now = new Date().getTime();
var pageLoadTime = now – window.performance.timing.navigationStart;

// Log the page load time
JL().info(window.location.pathname.split(“/”).pop() + ‘ load time-‘ + pageLoadTime + ‘ ms’);
}

This is what you will see in the log file:

{“r”:”index.htm”,”lg”:[{“l”:3000,”m”:”index.htm load time-384 ms”,”n”:””,”t”:1421544517551}]}

You may like following angularjs & SharePoint 2013 tutorials:

JavaScript logging is very easy with JSNLog library. Here we saw how to do error handling using JSNLog in AngualrJS.

Check out Best Alternative to InfoPath -> Try Now

free sharepoint training

SharePoint Online FREE Training

JOIN a FREE SharePoint Video Course (3 Part Video Series)

envelope
envelope

About Satyaprakash Samantaray

MVP Award Winner | Community Author & Contributor | Software Developer | Most Valuable Blogger(MVB)

View all posts by Satyaprakash Samantaray →