Case Study: Cross Browser Logging AMD Module

T

he console object, which allows developers to echo arbitrary pieces of data out to a console in the browser, is becoming a standard piece of equipment in most browsers. However, there are some browsers that do not have a console implemented, and leaving code that references the console object in browsers that do not have one will cause your javascript programs to die.

To normalize and remedy this pain point in development We will define a Log module to encapsulate the desired functionality. Or example will aim to mimic the api of the popular firebug development tool as well as provide a fallback for browsers that do not implement a logging console as well as for browsers that do implement one but may have it disabled. Our example will create an AMD module for use with RequireJS or Node.js

Initial Setup

To start we want to stub out the functions of the module .

define( ['require', 'exports'],function( require, exports){  
    exports.log = function(){

    };

    exports.info = function(){

    };

    exports.warn = function(){

    };

    exports.error = function(){

    };

    exports.debug = function(){

    };
});

Logging Abstraction

This module definition passes an anonymous function which exports a series of functions that will make up the api. Alternatively, the module could simply return an object containing the the function to make up the api. Our logging module will expose 5 functions: log, info, warn, error, and debug. All of this function essentially do the same thing, take any and all in coming arguments and attempt to push them out the a logging console. The only different being the name of the function that performs the action. To avoid re-writing the same function 5 times, we are going to add a private function within the module which will handle any logging function to be called easily.

define( ['require', 'exports'],function( require, exports){  
    var has_log = (function() {

      try {
        has_console = typeof console != null && 
                      typeof console.error === 'function';
      } catch (e) {
        return false;
      }

      return has_console;
    }());

    function log( ){
        var args = Array.prototype.slice.call( arguments );
        var fnName = args.shift();
        if( has_log){
            try{
                console[fnName].apply(console, args[0]);
            } catch ( e ){
                console.log( args[0] );
            }
        }
    };

    exports.log = function(){

    };

    exports.info = function(){

    };

    exports.warn = function(){

    };

    exports.error = function(){

    };

    exports.debug = function(){

    };
});

Our private log function takes the name of the logging function to use as the first argument and simply applies that function to the remaining arguments. If that fails, it will fall back to using the basic log function which we checked for with the has_log variable

API Implementation

From here, making the API do what we want is a simply a matter of implementing the log function in each situation.

define( ['require', 'exports'],function( require, exports){  
    var has_log = (function() {

      try {
        has_console = typeof console != null && 
                      typeof console.error === 'function';
      } catch (e) {
        return false;
      }

      return has_console;
    }());

    function log( ){
        var args = Array.prototype.slice.call( arguments );
        var fnName = args.shift();
        if( has_log){
            try{
                console[fnName].apply(console, args[0]);
            } catch ( e ){
                console.log( args[0] );
            }
        }
    };


    exports.log = function(){
        log('log', arguments);
        return this;
    };

    exports.info = function(){
        log('info', arguments);
        return this;
    };

    exports.warn =  function(){
        log('warn', arguments);
        return this;
    };

    exports.error = function(){
        log('error', arguments);
        return this;
    };

    exports.debug = function(){
        log('debug', arguments);
        return this;
    };

});

Logging Queue

If the browsers console is disabled or non-existent and the application is attempting to relay information, we still want to capture that information rather than just ignoring it. For this we will want to queue calls to the log function. Additionally when the console becomes available, we will want to dump any and all logged messages out to the logging console.

To do this we will need to add a private array to hold the calls as well as private functions to enable and disable the module's output.

define( ['require', 'exports'],function( require, exports){  
    var has_log = (function() {

        try {
            has_console = typeof console != null && 
                          typeof console.error === 'function';
        } catch (e) {
            return false;
        }

        return has_console;
    }());

    var enabled = has_log ? true : false;
    var _logged = [];

    function log( ){
        var args = Array.prototype.slice.call( arguments );
        var fnName = args.shift();
        if( has_log && enabled ) {
            try{
                console[fnName].apply(console, args[0]);
            } catch ( e ){
                console.log( args[0] );
                _logged.push( arguments )l
            }
        } else{
            _logged.push( arguments );
        }
    };

    function enableLog( ){
        enabled = true;
        for( var x = 0; x < _logged.length; x++ ){
            log.apply( this, args )
        }

        _logged = [];
        return this;
    };

    function disableLog( ){
        enabled = false;
        return this;
    };

    exports.enable = function(){
        enableLog();
        return this;
    };

    exports.disable = function(){
        disableLog();
        return this;
    };

    exports.log = function(){
        log('log', arguments);
        return this;
    };

    exports.info = function(){
        log('info', arguments);
        return this;
    };

    exports.warn = function(){
        log('warn', arguments);
        return this;
    };

    exports.error = function(){
        log('error', arguments);
        return this;
    };

    exports.debug = function(){
        log('debug', arguments);
        return this;
    };
});

Usage

Using the module is as simple as requiring it and using it just as if it were the console object.

require(['log'], function( log ){  
    log.log("Hello world");
    log.info("This is the %s log message", "second");
});
log amd commonjs console requirejs