Tuesday, 8 October 2013

Send Logging and Exceptions to server in AngularJS application

Introduction

AngularJS has some services for the logging ($log) and the exception handling ($exceptionHandler).  These services can be decorated, just as the services that you write, to extend the handling by sending the info the to backend server.
This kind of functionality can be very handy to get a log of all the messages and errors which occurred in the browser during testing of your app by the test team.
Those developers with a Java background, as I’m, are probably familiar with the Log4J framework to handle the logging in your application. There exists also a JavaScript version of it, log4javascript, and the cool thing is that they provide an AJAXAppender out of the box so that you can send logging info to the server.
This post shows you the necessary steps to setup the log4javascript framework together with AngularJS.

Set up

The log4javascript framework consist of a single javascript file that has all the required objects and functions to allow an increased experience. Add this file to your HTML, preferably before the creation of the AngularJS module as we will make a reference to a global variable which contains the entry points of log4javascript in the module configuration.
<head>
    <title>Angular - Log4js - Logging</title>

    <script src="js/libs/log4js/log4javascript.js"></script>
    <script src="js/libs/angular/angular.js"></script>
    <script src="js/app.js"></script>
    <script src="js/controller.js"></script>
</head>
<body ng-app="ng-logging">

AngularJS services can be decorate to override or add additional functionality with the configuration of your module.  This is the template that you can use for this purpose
var demo = angular.module('ng-logging', [ ]);

demo.config(function($provide) {
    $provide.decorator('$log', function($delegate, $myService) {
        // extend functionality or add functions to $delegate
        // Use additional code from the $myService service 
        return $delegate;
    });
});


Log4javascript

On the site of the framework, there is a pretty good description how you can use the framework and the different appenders.

The idea is that you supply some logging messages to the framework, and the, active at that moment, appenders handle the log message in some way.  In our case we are interested in sending the message to a server (AJAXAppender) but there exists also appenders for the console, a popup, an inline block, etc.. The additional functionality that an appender executes is the formatting of the message according to a certain ‘layout’ (date/time information, …)

The following lines prepares you a logger that sends the messages to a servlet listening on the loggingServlet URL.

        var log = log4javascript.getLogger();
        var ajaxAppender = new log4javascript.AjaxAppender("loggingServlet");
        ajaxAppender.setThreshold(log4javascript.Level.ALL);
        log.addAppender(ajaxAppender);


Extending AngularJS logger.

Since we have seen all the individual aspects, lets integrate them so that we can send logging messages to the servlet.

This is the code we have to use in our application to makes it happen.

demo.config(function($provide) {
    $provide.decorator('$log', function($delegate, $sniffer) {

        var _info = $delegate.info; //Saving the original behavior
        var _error = $delegate.error; //Saving the original behavior

        var log = log4javascript.getLogger();
        var ajaxAppender = new log4javascript.AjaxAppender("loggingServlet");
        ajaxAppender.setThreshold(log4javascript.Level.ALL);
        log.addAppender(ajaxAppender);

        $delegate.info = function(msg){
            _info(msg);
            log.info(msg);
        };

        $delegate.error = function(msg){
            _error(msg);
            log.error(msg);
        };

        return $delegate;
    });
});


We start by storing the reference to the original behaviour, the AngularJS default one, for info and error.
The next 4 lines are the one we need for having the Log4javascript framework initialized and a logger available that we can use.

And then we overwrite the info and error methods in the original implementation with custom ones where we handover the message to the logger and also call the saved, original behaviour.

Since it is a decorator, we need to return something. In this case the enhanced log service which gets injected in our code when we request the $log service.

LoggingServlet

On the server side, we know also need a servlet which captures the log messages and do something useful with it.  The capturing code looks like this:

@WebServlet(urlPatterns = "/loggingServlet")
public class LoggingServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String timestamp = req.getParameter("timestamp");
        String level = req.getParameter("level");
        String message = req.getParameter("message");

        Date clientTime = new Date(Long.valueOf(timestamp));
        System.out.println(clientTime + " - " + level + " - " + message);
    }
}


Of course, the above code doesn’t do anything useful but it is your starting point to save the data for example to a database when such a class is placed in a servlet 3.0 complaint container.

Capturing exceptions

For the capturing of exceptions, we can follow the same route as described above for the logging.

When we add an additional method to the logging services in the decorator, we can easily call this when there is an exception captured by the decorated $exceptionHandler.
        $delegate.fatal = function(msg, exception){ //Adding additional method
            _error(msg);
            log.error(msg, exception);
        };

The decoration of the $exceptionHandler is simpler as we just want to call our newly added fatal method of the log service.
demo.config(function($provide) {
    $provide.decorator('$exceptionHandler', function($delegate, $log) {
        return function(exception, cause) {
            $log.fatal(exception, cause);
            $delegate(cause, exception);

        };
    });
});

Conclusion

Due to the excellent modular structure of AngularJS and the ability to decorate services, we can create easily a system that sends any log and exception to the backend server with the help of Log4javascript.

The code for this post can be found on google code.