Monday, May 8, 2017

Capturing and logging unhandled exception in AngularJs

Everyone knows how important to capture and log unhandled exception in your code. Today we are going to learn how to do that using Angular. In addition, we will implement functionality, which will send unhandled exception to the server.

$exceptionHandler

According to AngularJs website example, it is possible to override standard behavior, but we do not want to do that because it is better to keep default implementation and create decorator, which will extend standard functionality. Please see code below:

(function () {
    'use strict';

    angular
        .module('app', [])
        .config(config);

    config.$inject = ['$provide'];

    function config($provide) {
        $provide.decorator('$exceptionHandler', myExceptionHandler);
    }

    myExceptionHandler.$inject = ['$delegate', '$window'];

    function myExceptionHandler($delegate, $window) {
        return function (exception, cause) {
            // Keep default implementation.
            $delegate(exception, cause);

            // Execute server side logging.
            // Need to wrap to try catch, to prevent infinitive loop.
            try {
                // Cannot use $http service because of circular references.
                $.ajax({
                    type: 'POST',
                    url: '/api/log/',
                    contentType: 'application/json',
                    data: angular.toJson({
                        url: $window.location.href,
                        message: exception.message,
                        stack: exception.stack,
                    })
                });
            }
            catch (e) {
                // Do nothing, browser will display message in console.
            }
        };
    }
})();

Resolving circular reference

The previous example uses jQuery for sending exception data to the server. What to do if you want to use $http?! The first idea, which comes to your mind is to write following code:

myExceptionHandler.$inject = ['$delegate', '$window', '$http'];

function myExceptionHandler($delegate, $window, $http) {
    // Code inside
}
This code will not work, because of circular dependency. Please find below code which resolves the issue:

(function () {
    'use strict';

    angular
        .module('app', [])
        .config(config);

    config.$inject = ['$provide'];

    function config($provide) {
        $provide.decorator('$exceptionHandler', myExceptionHandler);
    }

    myExceptionHandler.$inject = ['$delegate', '$window', '$injector'];

    function myExceptionHandler($delegate, $window, $injector) {
        return function (exception, cause) {
            // Keep default implementation.
            $delegate(exception, cause);

            // Execute server side logging.
            // Need to wrap to try catch, to prevent infinitive loop.
            try {
                // Using $injector to get $http service.
                var http = $injector.get('$http');
                http.post('/api/log/', {
                    url: $window.location.href,
                    message: exception.message,
                    stack: exception.stack,
                });
            }
            catch (e) {
                // Do nothing, browser will display message in console.
            }
        };
    }
})();

Code examples are uploaded to GitHub repository, please use https://github.com/aliakseimaniuk/blog-examples/tree/master/angularjs-exceptions link.