Back

TechnologyDec 15, 2014

Client-Side Error Logging With AngularJS

Ajay Babaria

Imagine you’re a web application developer doing product support. A customer runs into an issue, and while trying to replicate the problem you realize it’s too difficult to recreate. You’ll need some more information, specifically the console information. Wouldn’t it just be simpler if you had access to the client-log information in the first place? With AngularJS there is a very simple way of doing just that.

USING DECORATOR FOR ERROR LOGGING

To implement client-side error logging in an AngularJS application you will need to utilize the decorator service. More information about the decorator service can be found here.

Decorating allows you to modify the behavior of a service. In this particular case, functionality will be added to the $log service since it is used to display all messages to the console by Angular. In the $log service the various log-level functions (debug, info, warn, and error) will be wrapped so the messages are sent to the server using a POST call.

A SIMPLE STEP-BY-STEP IMPLEMENTATION OF CLIENT-SIDE LOGGING

First I will create a simple Angular module that depends on the ‘$provide’ service to wrap the $log service. The original service instance of $log will be injected into the $delegate variable.

'use strict';   angular.module('myLogger').config([ '$provide', function ( $provide ) { /* Information about AngularJS decorator can be found at: * https://docs.angularjs.org/api/auto/service/$provide */ $provide.decorator('$log', [ '$delegate', function ( // delegate is the original instance of $log $delegate ) {

Next, the original $log functions will be overwritten to add the Ajax call functionality. The code below utilizes the Underscore.JS library for simplicity

var levels = ['debug', 'info', 'warn', 'error']; var contentType = 'application/json; charset=UTF-8';   _.each(levels, function (level) {   // storing the original function var original = $delegate[level];   // creating a wrapped version of each $log level function // _.wrap is from the underscore.js library $delegate[level] = _.wrap(original, function (original) {   // logger data to be sent/logged to console var data = Array.prototype.slice.call(arguments, 1); // call to the original function which will write to the console original.apply($delegate, data);   /* 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: 'http://www.myURLforLoggingByType.com/logging/' + level, contentType: contentType, data: JSON.stringify({ message: JSON.stringify(data) }) }); }); });

Finally, the $delegate object is returned with the newly wrapped $log functions. Angular will automatically inject our decorated version of $log wherever $log is called.

// returning to $log object with the new wrapped log-level functions return $delegate; } ]); }]);

EXAMPLE OUTPUT

2014-12-09 13:49:49,982 ERROR - [“Cannot find account 101010”] 2014-12-09 14:21:10,896 INFO - [ “multiple account matches found”, { “byAddress”: [ 202020 ], “byPhone”: [ 202020, 303030 ] } ]

CONCLUSION

It is very straightforward to log all of the client-side messages to the server using Angular decoration. The biggest benefit is it makes it easier to find application errors once in production and accelerate bug finding. Client-side logging can also help identify the frequency of errors so hotfixes can be prioritized in the order of importance, helping you further improve your application for your clients.

FULL CODE:

'use strict';   angular.module('myLogger').config([ '$provide', function ( $provide ) { /* Information about AngularJS decorator can be found at: * https://docs.angularjs.org/api/auto/service/$provide */ $provide.decorator('$log', [ '$delegate', function ( // delegate is the original instance of $log $delegate ) { var levels = ['debug', 'info', 'warn', 'error']; var contentType = 'application/json; charset=UTF-8';   _.each(levels, function (level) {   // storing the original function var original = $delegate[level];   // creating a wrapped version of each $log level function // _.wrap is from the underscore.js library $delegate[level] = _.wrap(original, function (original) {   // logger data to be sent/logged to console var data = Array.prototype.slice.call(arguments, 1); // call to the original function which will write to the console original.apply($delegate, data);   /* 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: 'http://www.myURLforLoggingByType.com/logging/' + level, contentType: contentType, data: JSON.stringify({ message: JSON.stringify(data) }) }); }); }); // returning to $log object with the new wrapped log-level functions return $delegate; } ]); }]);