How to log client-side errors of AngularJS application to the server?
Has an end-user ever contacted you that they can’t use a feature or perform an action in your web application and neither they see any useful user-friendly message(s) to convey back to you that may help to find the origin of the problem? In this post I’ll explain you how to log client-side errors of AngularJS application to the server to make your web application better.
First of all, the situation mentioned above is a bad experience for your user. You may get to know about this actual issue if they report back to you. But many a times, they may just not bother to even raise a call to the concerned team about this issue. So, there are two-ways to get to know about a client-side bug – ask your user to open dev tools, go to console and take screenshot or copy the message to send it to you. There may be some errors logged in the browser’s console but it’s not their duty to open the developer tools and report the red coloured message back to you. Or a better way is to automate this process by logging any client-side errors on your server automatically so that your team can fix it similar to any server-side errors.
For any server-side project, developers take logging very seriously. If you are working on web applications using front-end frameworks, it’s for sure that your app will be JavaScript heavy. If any error occurs in the browser, then it’s your job to log it automatically to the server to review it later. I strongly recommend to read and watch my tutorial: How to handle exceptions in AngularJS using $exceptionHandler service?
The concept mentioned in the link above is a prerequisite to understand the concept discussed below as the code added below extends the example mentioned in that link.
Let’s look at a code snippet that I use in my angularjs application to log errors to the server:
var app = angular.module('plunker', []);
var StacktraceService = function() {}
StacktraceService.prototype.print = function($window, exception) {
return $window.printStackTrace({
e: exception
});
};
angular.module('plunker').service('stacktraceService', StacktraceService);
app.config(function($provide) {
// $provide provider is used to register the components in angular internally.
// use decorator to customise the behaviour of a service.
$provide.decorator('$exceptionHandler', ['$delegate', '$window', 'stacktraceService',
function($delegate, $window, stacktraceService) {
// exception: exception associated with the error
// cause: optional information about the context in which the error was thrown.
return function(exception, cause) {
// $delegate: provides the original service to the method which is used to call the base implementation
// of $exceptionHandler service which internally delegates to $log.error.
$delegate(exception, cause);
var stacktrace = stacktraceService.print($window, exception);
var clientSideErrorInfo = {
cause: cause || '', // the cause of the issue
message: exception.message, // the message in the exception
url: $window.location.href, // the location in the browser's address bar when error occurred
stacktrace: stacktrace.join('\n') // join array items to populate a string
};
console.log(clientSideErrorInfo.stacktrace);
// the angular $http service cannot be used in the $log
// decorator because it will cause a circular dependecy.
// to overcome this a direct ajax call should be made.
$.ajax({
type: 'POST',
url: '/logger/log', // this is the server end-point where you can log this error
contentType: 'application/json; charset=UTF-8',
data: JSON.stringify(clientSideErrorInfo)
});
};
}]);
});
app.controller('MainCtrl', function($scope) {
$scope.cancelBtnText = 'click me & check console';
$scope.cancelForm = function() {
$logs.info('user requested to cancel the form!');
};
});
Just a reminder, check the post that I’ve linked above so as to learn about $exceptionHandler service available in AngularJS framework. Let us discuss the important code statements in the snippet above.
What is Stacktrace Service?
var StacktraceService = function() {}
StacktraceService.prototype.print = function($window, exception) {
return $window.printStackTrace({
e: exception
});
};
angular.module('plunker').service('stacktraceService', StacktraceService);
// a call to printStackTrace function with exception object will output an array of
// string as shown below:
// ["ReferenceError: $logs is not defined", " at Scope.$scope.cancelForm (https://run.plnkr.co/iNUZMYs0Ss3pprmS/app.js:54:5)",
// " at fn (eval at <anonymous> (https://code.angularjs.org/1.4.9/angular.js:1:0) ]
I’ve added a StacktraceService which is being used in the app configuration. This is a custom service added to wrap the goodies provided by Stacktrace.js library. One of the important feature of this library is that you can easily get the full stacktrace from the error (Error object) which tells you a lot about the error’s origin, message and some other information. Now, you may think that the library is added in the html file so, you should just use it directly. But wait, the stacktrace library that we included in the html file is now in the Global scope. We don’t want to reference global objects inside the AngularJS components – that’s not how AngularJS rolls. As such, we want to wrap the stacktrace feature in a proper AngularJS service that formally exposes the print method which then internally calls the stacktrace library’s printStackTrace function by passing the exception object.
Prepare the data/information to log
// $delegate: provides the original service to the method which is used to call the base implementation
// of $exceptionHandler service which internally delegates to $log.error.
$delegate(exception, cause);
var stacktrace = stacktraceService.print($window, exception);
var clientSideErrorInfo = {
cause: cause || '', // the cause of the issue
message: exception.message, // the message in the exception
url: $window.location.href, // the location in the browser's address bar when error occurred
stacktrace: stacktrace.join('\n') // join array items to populate a string
};
console.log(clientSideErrorInfo.stacktrace); // logs the stacktrace - string type
Next, let us look at the implementation for app configuration. The call to $exceptionHandler service constructor using the $delegate is very important so as to report an exception. Make sure to understand the basics of $exceptionHandler service. After this call, we create an object that is also written on the server so that the data we send on HTTP POST is easily understandable by the server. This model definition is quite simple enough that aims to record the cause, message, url and the full stacktrace i.e. anything that you may need to log an error to the server. Depending on your application, you may have something less or more to log which eventually will help your team to resolve this issue. Before sending the stacktrace to the server, we perform a join on the array that is populated by the stacktrace service.
Send information to the server
$.ajax({
type: 'POST',
url: '/logger/log', // this is the server end-point where you can log this error
contentType: 'application/json; charset=UTF-8',
data: JSON.stringify(clientSideErrorInfo)
});
Well, we worked hard enough so as to eventually log the error information to the server. So, let’s do it. To achieve this, I’m using jQuery which is referenced in the html file. We use its ajax method that performs an HTTP POST action. Please make sure that you setup the right content-type else you will face invalid JSON primitive issue or similar kind of error. Also, make sure to call the JSON.stringify function on the data that you would like to send to the server which wraps the object in string. I’ve added a dummy url which you can replace with a valid end-point that exists on your server. I use ASP.NET Web API for this but this concept can be used with any server-side platform.
You may think that what is the need of using jQuery here just to make an ajax call and why not just use AngularJS $http in this case. It’s because the angular $http service cannot be used in the $log decorator because it will cause a circular dependency. To overcome this, a direct ajax call should be made.
Now, roll your sleeves up to see the hard work in action. Use the embedded sample, open the dev tools to see the console and click on the button, an exception is thrown because the code tries to use $logs instead of $log which is the logger service provided by AngularJS.
// a call to printStackTrace function with exception object will output an array of // string as shown below: // ["ReferenceError: $logs is not defined", " at Scope.$scope.cancelForm (https://run.plnkr.co/iNUZMYs0Ss3pprmS/app.js:54:5)", // " at fn (eval at <anonymous> (https://code.angularjs.org/1.4.9/angular.js:1:0),...]
Having this setup in the client-side web application, we now don’t need to worry about errors that use to go unnoticed because now there is no way that an error is encountered and we are not aware of it! If you followed all the steps above and from the linked post, you now have all the skills to log client-side errors of AngularJS application to the server.
