Mejorando el $log de AngularJS con decorators

La idea de este artículo es contar con un servicio de logging que nos permita contextualizar fácilmente la información que aparece en la consola del navegador.

A continuación pongo unos ejemplos para ilustrar su uso:

Uso básico:

$log.debug( 'línea debug' );
$log.info( 'línea info' );

Salida:

[0] -- 12/3/2016 15:43:38 línea debug
[1] -- 12/3/2016 15:43:38 línea info

Uso con contextos:

var logger = $log.getLogger( 'MainController' );
logger.debug( 'línea 1' );
logger.debug( 'línea 2' );
logger.debug( 'línea 3' );
 
var logger2 = $log.getLogger( 'App' );
logger2.debug( 'otra línea' );
logger2.debug( 'una línea más' );

Salida:

[0] -- 12/3/2016 15:58:05 -- [MainController]:0>  línea 1
[1] -- 12/3/2016 15:58:05 -- [MainController]:1>  línea 2
[2] -- 12/3/2016 15:58:05 -- [MainController]:2>  línea 3
[3] -- 12/3/2016 15:58:05 -- [App]:0>  otra línea
[4] -- 12/3/2016 15:58:05 -- [App]:1>  una línea más

Con desactivación de contextos:

var logger = $log.getLogger( 'MainController' );
logger.enable( false );
logger.debug( 'línea 1' );
logger.debug( 'línea 2' );
logger.enable( true );
logger.debug( 'línea 3' );
 
var logger2 = $log.getLogger( 'App' );
logger2.debug( 'otra línea' );
logger2.debug( 'una línea más' );

Salida:

[0] -- 12/3/2016 16:02:00 -- [MainController]:0>  línea 3
[1] -- 12/3/2016 16:02:00 -- [App]:0>  otra línea
[2] -- 12/3/2016 16:02:00 -- [App]:1>  una línea más

Los servicios en AngularJS se pueden mejorar de dos maneras: Usando decorators o providers.

La ventaja de usar un decorator es que sólo necesitas declararlo dentro del módulo de tu aplicación o incluir el módulo dónde está declarado como dependencia, para que entonces se decore el servicio deseado.

El código presentado a continuación utiliza un decorator:

( function() {
  'use strict';
 
  // Asume que el módulo de tu aplicación es llamado "myApp"
  // En este módulo se define el decorador
  angular.module( 'myApp' ).decorator( '$log', logDecorator );
 
  // La función "logDecorator" recibirá el servicio a decorar como
  // el parámetro $delegate, en este caso $delegate == $log
  function logDecorator( $delegate ) {
 
    // Contador general
    var n = 0;
 
    // El nuevo logger que imprime información básica
    var basicLogger = {
      log: basicDecoration( $delegate.log ),
      debug: basicDecoration( $delegate.debug ),
      info: basicDecoration( $delegate.info ),
      warn:basicDecoration( $delegate.warn ),
      error: basicDecoration( $delegate.error ),
      getLogger: getLogger,
      enabledContexts: {},
      counter: {}
    };
 
    // Esta función regresa el servicio de log básico mejorado
    return basicLogger;
 
 
    // La decoración básica incluye un contador, fecha y hora al inicio
    function basicDecoration( loggingFunction ) {
      return function() {
        var date = new Date();
        var args = Array.prototype.slice.call( arguments );
        args.unshift( '[' + n++ + '] -- ' + date.toLocaleString() );
        loggingFunction.apply( null, args );
      }
    }
 
    // La decoración contextual incluye info del contexto y un contador
    function contextDecoration( loggingFunction, context ) {
      return function() {
        var enabled = basicLogger.enabledContexts[context];
        if( enabled || enabled === undefined ) {
          var args = Array.prototype.slice.call( arguments );
          var contextInfo = '-- [' + context + ':';
          var counterInfo = basicLogger.counter[context]++ + '] >> ';
          args.unshift( contextInfo + counterInfo );
          loggingFunction.apply( null, args );
        }
      }
    }
 
    // Activa o desactiva el logging en un contexto determinado
    function enable( context ) {
      return function( enable ) {
        basicLogger.enabledContexts[context] = enable;
      }
    }
 
    // Regresa un logger para un contexto determinado
    function getLogger( context ) {
 
      // Contador contextual
      basicLogger.counter[context] = 0;
 
      // La nueva instancia de logger con información del contexto
      var contextLogger = {
        log: contextDecoration( basicLogger.log, context ),
        debug: contextDecoration( basicLogger.debug, context ),
        info: contextDecoration( basicLogger.info, context ),
        warn: contextDecoration( basicLogger.warn, context ),
        error: contextDecoration( basicLogger.error, context ),
        enable: enable( context )
      };
 
      // Esta función regresa el servicio de log contextualizado
      return contextLogger;
    }
  }
} )();

Una recomendación es que siempre utilices el servicio de AngularJS $log en vez del console.log, de esta manera siempre podrás desactivar todo el logging desde el bloque de configuración de tu aplicación con algo como esto:

angular.module( 'myApp', [] ).config( config );
 
function config( $logProvider ) {
  $logProvider.debugEnabled( false );
}

Con este nuevo logger también puedes mandar a consola toda actividad en tus controladores, directivas, servicios, etc., y si la información que aparece es demasiada, puedes desactivar los contextos que gustes desde el bloque de ejecución de tu aplicación.

Digamos que tienes un controlador llamado MainController, y en él inicializas un logger con contexto “MainController”. Para desactivar toda la salida a consola generada por ese controlador, puedes hacer algo así:

angular.module( 'myApp', [] ).config( config ).run( run );
 
function config( $logProvider ) {
  $logProvider.debugEnabled( true );
}
 
function run( $log ) {
  var logger = $log.getLogger( 'MainController' );
  logger.enable( false );
}

La idea es que en cada componente de tu aplicación tengas un logger y el contexto sea el nombre del componente.

Referencias

Enhancing AngularJS Logging using Decorators
Enhancing $log in AngularJs the simple way

Deja un comentario