Node Monitor v0.6.5

Show:

File: lib/Log.js

/*jslint browser: true */
// Log.js (c) 2010-2014 Loren West and other contributors
// May be freely distributed under the MIT license.
// For further details and documentation:
// http://lorenwest.github.com/node-monitor
(function(root){

  // Module loading
  var Monitor = root.Monitor || require('./Monitor'),
      // Raw events on the server (for speed), backbone events on the browser (for functionality)
      EventEmitter = Monitor.commonJS ? require('events').EventEmitter.prototype : Monitor.Backbone.Events,
      Stat = Monitor.Stat,
      stat = new Stat('Log'),
      _ = Monitor._,
      emittingNow = false;

  /**
  * A lightweight component for gathering and emitting application logs
  *
  * It's designed with low development and runtime cost in mind, encouraging
  * usage with minimum concern for overhead.  Runtime monitoring can be as chatty
  * as desired, outputting every log statement of every type, or finely tuned
  * with regular expressions to monitor specific log statements.
  *
  * Log Collector
  * -------------
  *
  * As a collector, it's a place to send application logs.
  *
  * Example for outputting a log in your application:
  *
  *     var log = require('monitor').getLogger('myModule');
  *     ...
  *     log.info('Credit limit accepted', limit, requestedAmount);
  *
  * The above is a request to output an ```info``` log for ```myModule``` named
  * ```Credit limit accepted```.  The log entry includes all additional parameters,
  * in this case the customer credit limit and the reqeusted amount.
  *
  * The full name for this log entry is: ```"info.myModule.Credit limit accepted"```
  * The name is important, as monitors can be configured to output logs based
  * on this name.
  *
  * Best practices are to include dynamic parameters in extra arguments
  * vs. concatenating strings.  This reduces logging overhead, especially
  * for log statements that aren't currently being watched.
  *
  * Log Emitter
  * -----------
  * As an emitter, the Log module is a place to capture logging output.
  *
  * When listening for log entries, wildcards can be used to register for
  * particular log types and entries.
  *
  *     var Log = require('monitor').Log;
  *     ...
  *     Log.on('info.myModule.*', myFunction);
  *
  * Will call ```myFunction``` when all ```info.myModule.*``` logs are emitted.
  *
  * Listeners are invoked with the following arguments:
  *
  * - type - The log type (trace, debug, info, warn, error, or fatal)
  * - module - The logger module name
  * - name - The log entry name
  * - args... - Additional arguments passed into the log entry are passed on
  *             as additional args to the event listener.
  *
  * Wildcards
  * ---------
  * A flexible and user-oriented wildcard pattern is used for monitoring
  * logs.  The pattern is described in the <a href="Stat.html#wildcards">Wildcard secttion of the Stats class</a>.
  *
  * Choosing Good Names
  * -------------------
  * It's a good idea to pick a good naming scheme with each dot-delimited segment
  * having a consistent, well-defined purpose.  Volatile segments should be as deep
  * into the hierarchy (furthest right) as possible.  Keeping the names less
  * volatile makes it easier to turn statistics recording on for all logs.
  *
  * @class Log
  * @constructor
  */
  var Log = Monitor.Log = function(module) {
    var t = this;
    t.module = module;
  };
  var proto = Log.prototype;

  // This is a map of registered event names to compiled regexs, for
  // quickly testing if a log needs to be emitted.
  Log.eventRegex = {};

  /**
  * Output a ```trace``` log entry
  *
  * @method trace
  * @param name {String} Log entry name
  * @param [...] {Any} Subsequent arguments to add to the log
  */

  /**
  * Output a ```debug``` log entry
  *
  * @method debug
  * @param name {String} Log entry name
  * @param [...] {Any} Subsequent arguments to add to the log
  */

  /**
  * Output a ```info``` log entry
  *
  * @method info
  * @param name {String} Log entry name
  * @param [...] {Any} Subsequent arguments to add to the log
  */

  /**
  * Output a ```warn``` log entry
  *
  * @method warn
  * @param name {String} Log entry name
  * @param [...] {Any} Subsequent arguments to add to the log
  */

  /**
  * Output a ```error``` log entry
  *
  * @method error
  * @param name {String} Log entry name
  * @param [...] {Any} Subsequent arguments to add to the log
  */

  /**
  * Output a ```fatal``` log entry
  *
  * @method fatal
  * @param name {String} Log entry name
  * @param [...] {Any} Subsequent arguments to add to the log
  */

  // Add a method for each log type
  ['trace','debug','info','warn','error','fatal'].forEach(function(method) {
    proto[method] = function(name) {
      Log._emit(method, this.module, name, arguments);
    };
  });

  /**
  * Send the log to all registered listeners
  *
  * @private
  * @static
  * @method emit
  * @param type {string} The log type (trace, debug, info, etc)
  * @param module {String} The log module name
  * @param name {String} The log entry name
  * @param args {any[]} Arguments to the log entry
  */
  Log._emit = function(type, module, name, args) {
    var eventName,
        fullName = type + '.' + module + '.' + name;

    // Prevent log recursion. This has the effect of disabling all logging
    // for log handlers (and their downstream effect), but is necessary to
    // prevent infinite recursion.  If it's desired to log the output of
    // log handlers, then delay that processing until nextTick.
    if (emittingNow) {
      return;
    }
    emittingNow = true;

    // Output a counter stat for this log
    stat.increment(fullName);

    // Test the name against all registered events
    for (eventName in Log._events) {

      // Get the regex associated with the name (using the Stat package)
      var regex = Log.eventRegex[eventName];
      if (!regex) {
        regex = Log.eventRegex[eventName] = Stat._buildRegex(eventName);
      }

      // Test the long name with the regex, and emit if it matches
      if (regex.test(fullName)) {

        // Build the arguments as event name, log type, module, name, [other args...]
        var allArgs = _.toArray(args),
            emitFn = Log.emit || Log.trigger; // NodeJS/server=emit, Backbone/browser=trigger
        allArgs.splice(0, 1, eventName, type, module, name);
        emitFn.apply(Log, allArgs);
      }
    }

    // Turn off recursion prevention
    emittingNow = false;
  };

  // Mixin event processing for the Log class
  _.extend(Log, EventEmitter);

  // Expose this class from the Monitor module
  Monitor.setLoggerClass(Log);

  /**
  * Output log statements to the console
  *
  * This method can be used as a listener to send logs to the console.
  *
  * It uses console.error() for error and fatal log types, and console.log()
  * for all other log types.
  *
  * Example:
  *
  *     var Log = Monitor.Log;
  *     Log.on('*.MyModule.*', Log.console);
  *
  * @static
  * @method consoleLogger
  * @param type {string} The log type (trace, debug, info, etc)
  * @param module {String} The log module name
  * @param name {String} The log entry name
  * @param args {any...} All original, starting with the short name
  */
  Log.console = function(type, module, name) {

    // Build the string to log, in log4js format
    var nowStr = (new Date()).toJSON(),
        args = _.toArray(arguments),
        logStr = '[' + nowStr + '] [' + type.toUpperCase() + '] ' + module;

    // Remove the type, module, name leaving the args to the log
    args.splice(0,3);

    // If no args, then they didn't provide a name
    if (args.length === 0) {
      args = [name];
    }
    else {
      // Add the log entry name
      logStr += '.' + name;
    }

    // If the output is simple, just print it.  Otherwise JSON.stringify it.
    logStr += ' - ';
    if (args.length === 1 && typeof args[0] === 'string') {
      logStr += args[0];
    }
    else {
      try {
        logStr += JSON.stringify(args);
      } catch(e) {
        logStr += Monitor.stringify(args);
      }
    }

    // Send to the console - Log or error
    if (type === 'error' || type === 'fatal') {
      console.error(logStr);
    }
    else {
      console.log(logStr);
    }

  };

  // Attach the console log listener
  var pattern = Monitor.Config.Monitor.consoleLogListener.pattern;
  if (pattern) {
    Log.on(pattern, Log.console);
  }

}(this));