/*
 * Copyright (c) 2012 Trent Mick. All rights reserved.
 *
 * The bunyan logging library for node.js.
 */

var VERSION = '0.13.0';

// Bunyan log format version. This becomes the 'v' field on all log records.
// `0` is until I release a version '1.0.0' of node-bunyan. Thereafter,
// starting with `1`, this will be incremented if there is any backward
// incompatible change to the log record format. Details will be in
// 'CHANGES.md' (the change log).
var LOG_VERSION = 0;


var xxx = function xxx(s) {     // internal dev/debug logging
  var args = ['XX' + 'X: '+s].concat(Array.prototype.slice.call(arguments, 1));
  console.error.apply(this, args);
};
var xxx = function xxx() {};  // uncomment to turn of debug logging


var os = require('os');
var fs = require('fs');
var util = require('util');
var assert = require('assert');
var EventEmitter = require('events').EventEmitter;



//---- Internal support stuff

function objCopy(obj) {
  if (obj === null) {
    return null;
  } else if (Array.isArray(obj)) {
    return obj.slice();
  } else {
    var copy = {};
    Object.keys(obj).forEach(function (k) {
      copy[k] = obj[k];
    });
    return copy;
  }
}

var format = util.format;
if (!format) {
  // If not node 0.6, then use its `util.format`:
  // <https://github.com/joyent/node/blob/master/lib/util.js#L22>:
  var inspect = util.inspect;
  var formatRegExp = /%[sdj%]/g;
  format = function format(f) {
    if (typeof (f) !== 'string') {
      var objects = [];
      for (var i = 0; i < arguments.length; i++) {
        objects.push(inspect(arguments[i]));
      }
      return objects.join(' ');
    }

    var i = 1;
    var args = arguments;
    var len = args.length;
    var str = String(f).replace(formatRegExp, function (x) {
      if (i >= len)
        return x;
      switch (x) {
        case '%s': return String(args[i++]);
        case '%d': return Number(args[i++]);
        case '%j': return JSON.stringify(args[i++]);
        case '%%': return '%';
        default:
          return x;
      }
    });
    for (var x = args[i]; i < len; x = args[++i]) {
      if (x === null || typeof (x) !== 'object') {
        str += ' ' + x;
      } else {
        str += ' ' + inspect(x);
      }
    }
    return str;
  };
}


/**
 * Gather some caller info 3 stack levels up.
 * See <http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi>.
 */
function getCaller3Info() {
  var obj = {};
  var saveLimit = Error.stackTraceLimit;
  var savePrepare = Error.prepareStackTrace;
  Error.stackTraceLimit = 3;
  Error.captureStackTrace(this, getCaller3Info);
  Error.prepareStackTrace = function (_, stack) {
    var caller = stack[2];
    obj.file = caller.getFileName();
    obj.line = caller.getLineNumber();
    var func = caller.getFunctionName();
    if (func)
      obj.func = func;
  };
  this.stack;
  Error.stackTraceLimit = saveLimit;
  Error.prepareStackTrace = savePrepare;
  return obj;
}


/**
 * Warn about an bunyan processing error.
 *
 * If file/line are given, this makes an attempt to warn on stderr only once.
 *
 * @param msg {String} Message with which to warn.
 * @param file {String} Optional. File path relevant for the warning.
 * @param line {String} Optional. Line number in `file` path relevant for
 *    the warning.
 */
function _warn(msg, file, line) {
  assert.ok(msg);
  var key;
  if (file && line) {
    key = file + ':' + line;
    if (_warned[key]) {
      return;
    }
    _warned[key] = true;
  }
  process.stderr.write(msg + '\n');
}
var _warned = {};



//---- Levels

var TRACE = 10;
var DEBUG = 20;
var INFO = 30;
var WARN = 40;
var ERROR = 50;
var FATAL = 60;

var levelFromName = {
  'trace': TRACE,
  'debug': DEBUG,
  'info': INFO,
  'warn': WARN,
  'error': ERROR,
  'fatal': FATAL
};

function resolveLevel(nameOrNum) {
  var level = (typeof (nameOrNum) === 'string'
      ? levelFromName[nameOrNum]
      : nameOrNum);
  if (! (TRACE <= level && level <= FATAL)) {
    throw new Error('invalid level: ' + nameOrNum);
  }
  return level;
}



//---- Logger class

/**
 * Create a Logger instance.
 *
 * @param options {Object} See documentation for full details. At minimum
 *    this must include a 'name' string key. Configuration keys:
 *      - `streams`: specify the logger output streams. This is an array of
 *        objects with these fields:
 *          - `type`: The stream type. See README.md for full details.
 *            Often this is implied by the other fields. Examples are
 *            "file", "stream" and "raw".
 *          - `level`: Defaults to "info".
 *          - `path` or `stream`: The specify the file path or writeable
 *            stream to which log records are written. E.g.
 *            `stream: process.stdout`.
 *          - `closeOnExit` (boolean): Optional. Default is true for a
 *            "file" stream when `path` is given, false otherwise.
 *        See README.md for full details.
 *      - `level`: set the level for a single output stream (cannot be used
 *        with `streams`)
 *      - `stream`: the output stream for a logger with just one, e.g.
 *        `process.stdout` (cannot be used with `streams`)
 *      - `serializers`: object mapping log record field names to
 *        serializing functions. See README.md for details.
 *      - `src`: Boolean (default false). Set true to enable 'src' automatic
 *        field with log call source info.
 *    All other keys are log record fields.
 *
 * An alternative *internal* call signature is used for creating a child:
 *    new Logger(<parent logger>, <child options>[, <child opts are simple>]);
 *
 * @param _childSimple (Boolean) An assertion that the given `_childOptions`
 *    (a) only add fields (no config) and (b) no serialization handling is
 *    required for them. IOW, this is a fast path for frequent child
 *    creation.
 */
function Logger(options, _childOptions, _childSimple) {
  xxx('Logger start:', options)
  if (! this instanceof Logger) {
    return new Logger(options, _childOptions);
  }

  // Input arg validation.
  var parent;
  if (_childOptions !== undefined) {
    parent = options;
    options = _childOptions;
    if (! parent instanceof Logger) {
      throw new TypeError('invalid Logger creation: do not pass a second arg');
    }
  }
  if (!options) {
    throw new TypeError('options (object) is required');
  }
  if (!parent) {
    if (!options.name) {
      throw new TypeError('options.name (string) is required');
    }
  } else {
    if (options.name) {
      throw new TypeError('invalid options.name: child cannot set logger name');
    }
  }
  if ((options.stream || options.level) && options.streams) {
    throw new TypeError(
      'cannot mix "streams" with "stream" or "level" options');
  }
  if (options.streams && !Array.isArray(options.streams)) {
    throw new TypeError('invalid options.streams: must be an array')
  }
  if (options.serializers && (typeof (options.serializers) !== 'object' ||
      Array.isArray(options.serializers))) {
    throw new TypeError('invalid options.serializers: must be an object')
  }

  EventEmitter.call(this);

  // Fast path for simple child creation.
  if (parent && _childSimple) {
    // `_isSimpleChild` is a signal to stream close handling that this child
    // owns none of its streams.
    this._isSimpleChild = true;

    this._level = parent._level;
    this.streams = parent.streams;
    this.serializers = parent.serializers;
    this.src = parent.src;
    var fields = this.fields = {};
    var parentFieldNames = Object.keys(parent.fields);
    for (var i = 0; i < parentFieldNames.length; i++) {
      var name = parentFieldNames[i];
      fields[name] = parent.fields[name];
    }
    var names = Object.keys(options);
    for (var i = 0; i < names.length; i++) {
      var name = names[i];
      fields[name] = options[name];
    }
    return;
  }

  // Null values.
  var self = this;
  if (parent) {
    this._level = parent._level;
    this.streams = [];
    for (var i = 0; i < parent.streams.length; i++) {
      var s = objCopy(parent.streams[i]);
      s.closeOnExit = false; // Don't own parent stream.
      this.streams.push(s);
    }
    this.serializers = objCopy(parent.serializers);
    this.src = parent.src;
    this.fields = objCopy(parent.fields);
  } else {
    this._level = Number.POSITIVE_INFINITY;
    this.streams = [];
    this.serializers = null;
    this.src = false;
    this.fields = {};
  }

  // Helpers
  function addStream(s) {
    s = objCopy(s);

    // Implicit 'type' from other args.
    var type = s.type;
    if (!s.type) {
      if (s.stream) {
        s.type = 'stream';
      } else if (s.path) {
        s.type = 'file'
      }
    }
    s.raw = (s.type === 'raw');  // PERF: Allow for faster check in `_emit`.

    if (s.level) {
      s.level = resolveLevel(s.level);
    } else {
      s.level = INFO;
    }
    if (s.level < self._level) {
      self._level = s.level;
    }

    switch (s.type) {
    case 'stream':
      if (!s.closeOnExit) {
        s.closeOnExit = false;
      }
      break;
    case 'file':
      if (!s.stream) {
        s.stream = fs.createWriteStream(s.path,
          {flags: 'a', encoding: 'utf8'});
        s.stream.on('error', function (err) {
          self.emit('error', err, s);
        });
        if (!s.closeOnExit) {
          s.closeOnExit = true;
        }
      } else {
        if (!s.closeOnExit) {
          s.closeOnExit = false;
        }
      }
      break;
    case 'raw':
      if (!s.closeOnExit) {
        s.closeOnExit = false;
      }
      break;
    default:
      throw new TypeError('unknown stream type "' + s.type + '"');
    }

    self.streams.push(s);
  }

  function addSerializers(serializers) {
    if (!self.serializers) {
      self.serializers = {};
    }
    Object.keys(serializers).forEach(function (field) {
      var serializer = serializers[field];
      if (typeof (serializer) !== 'function') {
        throw new TypeError(format(
          'invalid serializer for "%s" field: must be a function', field));
      } else {
        self.serializers[field] = serializer;
      }
    });
  }

  // Handle *config* options.
  if (options.stream) {
    addStream({
      type: 'stream',
      stream: options.stream,
      closeOnExit: false,
      level: (options.level ? resolveLevel(options.level) : INFO)
    });
  } else if (options.streams) {
    options.streams.forEach(addStream);
  } else if (!parent) {
    addStream({
      type: 'stream',
      stream: process.stdout,
      closeOnExit: false,
      level: (options.level ? resolveLevel(options.level) : INFO)
    });
  }
  if (options.serializers) {
    addSerializers(options.serializers);
  }
  if (options.src) {
    this.src = true;
  }
  xxx('Logger: ', self)

  // Fields.
  // These are the default fields for log records (minus the attributes
  // removed in this constructor). To allow storing raw log records
  // (unrendered), `this.fields` must never be mutated. Create a copy for
  // any changes.
  var fields = objCopy(options);
  delete fields.stream;
  delete fields.level;
  delete fields.streams;
  delete fields.serializers;
  delete fields.src;
  if (this.serializers) {
    this._applySerializers(fields);
  }
  if (!fields.hostname) {
    fields.hostname = os.hostname();
  }
  if (!fields.pid) {
    fields.pid = process.pid;
  }
  Object.keys(fields).forEach(function (k) {
    self.fields[k] = fields[k];
  });
}

util.inherits(Logger, EventEmitter);


/**
 * Create a child logger, typically to add a few log record fields.
 *
 * This can be useful when passing a logger to a sub-component, e.g. a
 * 'wuzzle' component of your service:
 *
 *    var wuzzleLog = log.child({component: 'wuzzle'})
 *    var wuzzle = new Wuzzle({..., log: wuzzleLog})
 *
 * Then log records from the wuzzle code will have the same structure as
 * the app log, *plus the component='wuzzle' field*.
 *
 * @param options {Object} Optional. Set of options to apply to the child.
 *    All of the same options for a new Logger apply here. Notes:
 *      - The parent's streams are inherited and cannot be removed in this
 *        call.
 *      - The parent's serializers are inherited, though can effectively be
 *        overwritten by using duplicate keys.
 * @param simple {Boolean} Optional. Set to true to assert that `options`
 *    (a) only add fields (no config) and (b) no serialization handling is
 *    required for them. IOW, this is a fast path for frequent child
 *    creation. See 'tools/timechild.js' for numbers.
 */
Logger.prototype.child = function (options, simple) {
  return new Logger(this, options || {}, simple);
}


/* BEGIN JSSTYLED */
/**
 * Close this logger.
 *
 * This closes streams (that it owns, as per 'endOnClose' attributes on
 * streams), etc. Typically you **don't** need to bother calling this.
Logger.prototype.close = function () {
  if (this._closed) {
    return;
  }
  if (!this._isSimpleChild) {
    self.streams.forEach(function (s) {
      if (s.endOnClose) {
        xxx('closing stream s:', s);
        s.stream.end();
        s.endOnClose = false;
      }
    });
  }
  this._closed = true;
}
 */
/* END JSSTYLED */


/**
 * Get/set the level of all streams on this logger.
 *
 * Get Usage:
 *    // Returns the current log level (lowest level of all its streams).
 *    log.level() -> INFO
 *
 * Set Usage:
 *    log.level(INFO)       // set all streams to level INFO
 *    log.level('info')     // can use 'info' et al aliases
 */
Logger.prototype.level = function level(value) {
  if (value === undefined) {
    return this._level;
  }
  var newLevel = resolveLevel(value);
  var len = this.streams.length;
  for (var i = 0; i < len; i++) {
    this.streams[i].level = newLevel;
  }
  this._level = newLevel;
}


/**
 * Get/set the level of a particular stream on this logger.
 *
 * Get Usage:
 *    // Returns an array of the levels of each stream.
 *    log.levels() -> [TRACE, INFO]
 *
 *    // Returns a level of the identified stream.
 *    log.levels(0) -> TRACE      // level of stream at index 0
 *    log.levels('foo')           // level of stream with name 'foo'
 *
 * Set Usage:
 *    log.levels(0, INFO)         // set level of stream 0 to INFO
 *    log.levels(0, 'info')       // can use 'info' et al aliases
 *    log.levels('foo', WARN)     // set stream named 'foo' to WARN
 *
 * Stream names: When streams are defined, they can optionally be given
 * a name. For example,
 *       log = new Logger({
 *         streams: [
 *           {
 *             name: 'foo',
 *             path: '/var/log/my-service/foo.log'
 *             level: 'trace'
 *           },
 *         ...
 *
 * @param name {String|Number} The stream index or name.
 * @param value {Number|String} The level value (INFO) or alias ('info').
 *    If not given, this is a 'get' operation.
 * @throws {Error} If there is no stream with the given name.
 */
Logger.prototype.levels = function levels(name, value) {
  if (name === undefined) {
    assert.equal(value, undefined);
    return this.streams.map(
      function (s) { return s.level });
  }
  var stream;
  if (typeof (name) === 'number') {
    stream = this.streams[name];
    if (stream === undefined) {
      throw new Error('invalid stream index: ' + name);
    }
  } else {
    var len = this.streams.length;
    for (var i = 0; i < len; i++) {
      var s = this.streams[i];
      if (s.name === name) {
        stream = s;
        break;
      }
    }
    if (!stream) {
      throw new Error(format('no stream with name "%s"', name));
    }
  }
  if (value === undefined) {
    return stream.level;
  } else {
    var newLevel = resolveLevel(value);
    stream.level = newLevel;
    if (newLevel < this._level) {
      this._level = newLevel;
    }
  }
}


/**
 * Apply registered serializers to the appropriate keys in the given fields.
 *
 * Pre-condition: This is only called if there is at least one serializer.
 *
 * @param fields (Object) The log record fields.
 * @param keys (Array) Optional array of keys to which to limit processing.
 */
Logger.prototype._applySerializers = function (fields, keys) {
  var self = this;

  // Mapping of keys to potentially serialize.
  var applyKeys = fields;
  if (keys) {
    applyKeys = {};
    for (var i = 0; i < keys.length; i++) {
      applyKeys[keys[i]] = true;
    }
  }

  xxx('_applySerializers: applyKeys', applyKeys);

  // Check each serializer against these (presuming number of serializers
  // is typically less than number of fields).
  Object.keys(this.serializers).forEach(function (name) {
    if (applyKeys[name]) {
      xxx('_applySerializers; apply to "%s" key', name)
      try {
        fields[name] = self.serializers[name](fields[name]);
      } catch (err) {
        _warn(format('bunyan: ERROR: This should never happen. '
          + 'This is a bug in <https://github.com/trentm/node-bunyan> or '
          + 'in this application. Exception from "%s" Logger serializer: %s',
          name, err.stack || err));
        fields[name] = format('(Error in Bunyan log "%s" serializer '
          + 'broke field. See stderr for details.)', name);
      }
    }
  });
}


/**
 * A log record is a 4-tuple:
 *    [<default fields object>,
 *     <log record fields object>,
 *     <level integer>,
 *     <msg args array>]
 * For Perf reasons, we only render this down to a single object when
 * it is emitted.
 */
Logger.prototype._mkRecord = function (fields, level, msgArgs) {
  var recFields = (fields ? objCopy(fields) : null);
  return [this.fields, recFields, level, msgArgs];
}


/**
 * Emit a log record.
 *
 * @param rec {log record}
 */
Logger.prototype._emit = function (rec) {
  var i;

  var obj = objCopy(rec[0]);
  var level = obj.level = rec[2];
  var recFields = rec[1];
  if (recFields) {
    if (this.serializers) {
      this._applySerializers(recFields);
    }
    Object.keys(recFields).forEach(function (k) {
      obj[k] = recFields[k];
    });
  }
  xxx('Record:', rec)
  obj.msg = format.apply(this, rec[3]);
  if (!obj.time) {
    obj.time = (new Date());
  }
  // Get call source info
  if (this.src && !obj.src) {
    obj.src = getCaller3Info()
  }
  obj.v = LOG_VERSION;

  // Lazily determine if this Logger has non-"raw" streams. If there are
  // any, then we need to stringify the log record.
  if (this.haveNonRawStreams === undefined) {
    this.haveNonRawStreams = false;
    for (i = 0; i < this.streams.length; i++) {
      if (!this.streams[i].raw) {
        this.haveNonRawStreams = true;
        break;
      }
    }
  }

  // Stringify the object. Attempt to warn/recover on error.
  var str;
  if (this.haveNonRawStreams) {
    try {
      str = JSON.stringify(obj) + '\n';
    } catch (e) {
      var src = ((obj.src && obj.src.file) ? obj.src : getCaller3Info());
      var emsg = format('bunyan: ERROR: could not stringify log record from '
        + '%s:%d: %s', src.file, src.line, e);
      var eobj = objCopy(rec[0]);
      eobj.bunyanMsg = emsg;
      eobj.msg = obj.msg;
      eobj.time = obj.time;
      eobj.v = LOG_VERSION;
      _warn(emsg, src.file, src.line);
      try {
        str = JSON.stringify(eobj) + '\n';
      } catch (e2) {
        str = JSON.stringify({bunyanMsg: emsg});
      }
    }
  }

  for (i = 0; i < this.streams.length; i++) {
    var s = this.streams[i];
    if (s.level <= level) {
      xxx('writing log rec "%s" to "%s" stream (%d <= %d): %j',
        obj.msg, s.type, s.level, level, obj);
      s.stream.write(s.raw ? obj : str);
    }
  };
}


/**
 * Log a record at TRACE level.
 *
 * Usages:
 *    log.trace()  -> boolean is-trace-enabled
 *    log.trace(<Error> err, [<string> msg, ...])
 *    log.trace(<string> msg, ...)
 *    log.trace(<object> fields, <string> msg, ...)
 *
 * @params fields {Object} Optional set of additional fields to log.
 * @params msg {String} Log message. This can be followed by additional
 *    arguments that are handled like
 *    [util.format](http://nodejs.org/docs/latest/api/all.html#util.format).
 */
Logger.prototype.trace = function () {
  var fields = null, msgArgs = null;
  if (arguments.length === 0) {   // `log.trace()`
    return (this._level <= TRACE);
  } else if (this._level > TRACE) {
    return;
  } else if (arguments[0] instanceof Error) {
    // `log.trace(err, ...)`
    fields = {err: errSerializer(arguments[0])};
    if (arguments.length === 1) {
      msgArgs = [fields.err.message];
    } else {
      msgArgs = Array.prototype.slice.call(arguments, 1);
    }
  } else if (typeof (arguments[0]) === 'string') {  // `log.trace(msg, ...)`
    fields = null;
    msgArgs = Array.prototype.slice.call(arguments);
  } else {  // `log.trace(fields, msg, ...)`
    fields = arguments[0];
    msgArgs = Array.prototype.slice.call(arguments, 1);
  }
  var rec = this._mkRecord(fields, TRACE, msgArgs);
  this._emit(rec);
}

/**
 * Log a record at DEBUG level.
 *
 * Usages:
 *    log.debug()  -> boolean is-debug-enabled
 *    log.debug(<Error> err, [<string> msg, ...])
 *    log.debug(<string> msg, ...)
 *    log.debug(<object> fields, <string> msg, ...)
 *
 * @params fields {Object} Optional set of additional fields to log.
 * @params msg {String} Log message. This can be followed by additional
 *    arguments that are handled like
 *    [util.format](http://nodejs.org/docs/latest/api/all.html#util.format).
 */
Logger.prototype.debug = function () {
  var fields = null, msgArgs = null;
  if (arguments.length === 0) {   // `log.debug()`
    return (this._level <= DEBUG);
  } else if (this._level > DEBUG) {
    return;
  } else if (arguments[0] instanceof Error) {
    // `log.debug(err, ...)`
    fields = {err: errSerializer(arguments[0])};
    if (arguments.length === 1) {
      msgArgs = [fields.err.message];
    } else {
      msgArgs = Array.prototype.slice.call(arguments, 1);
    }
  } else if (typeof (arguments[0]) === 'string') {  // `log.debug(msg, ...)`
    fields = null;
    msgArgs = Array.prototype.slice.call(arguments);
  } else {  // `log.debug(fields, msg, ...)`
    fields = arguments[0];
    msgArgs = Array.prototype.slice.call(arguments, 1);
  }
  var rec = this._mkRecord(fields, DEBUG, msgArgs);
  this._emit(rec);
}

/**
 * Log a record at INFO level.
 *
 * Usages:
 *    log.info()  -> boolean is-info-enabled
 *    log.info(<Error> err, [<string> msg, ...])
 *    log.info(<string> msg, ...)
 *    log.info(<object> fields, <string> msg, ...)
 *
 * @params fields {Object} Optional set of additional fields to log.
 * @params msg {String} Log message. This can be followed by additional
 *    arguments that are handled like
 *    [util.format](http://nodejs.org/docs/latest/api/all.html#util.format).
 */
Logger.prototype.info = function () {
  var fields = null, msgArgs = null;
  if (arguments.length === 0) {   // `log.info()`
    return (this._level <= INFO);
  } else if (this._level > INFO) {
    return;
  } else if (arguments[0] instanceof Error) {
    // `log.info(err, ...)`
    fields = {err: errSerializer(arguments[0])};
    if (arguments.length === 1) {
      msgArgs = [fields.err.message];
    } else {
      msgArgs = Array.prototype.slice.call(arguments, 1);
    }
  } else if (typeof (arguments[0]) === 'string') {  // `log.info(msg, ...)`
    fields = null;
    msgArgs = Array.prototype.slice.call(arguments);
  } else {  // `log.info(fields, msg, ...)`
    fields = arguments[0];
    msgArgs = Array.prototype.slice.call(arguments, 1);
  }
  var rec = this._mkRecord(fields, INFO, msgArgs);
  this._emit(rec);
}

/**
 * Log a record at WARN level.
 *
 * Usages:
 *    log.warn()  -> boolean is-warn-enabled
 *    log.warn(<Error> err, [<string> msg, ...])
 *    log.warn(<string> msg, ...)
 *    log.warn(<object> fields, <string> msg, ...)
 *
 * @params fields {Object} Optional set of additional fields to log.
 * @params msg {String} Log message. This can be followed by additional
 *    arguments that are handled like
 *    [util.format](http://nodejs.org/docs/latest/api/all.html#util.format).
 */
Logger.prototype.warn = function () {
  var fields = null, msgArgs = null;
  if (arguments.length === 0) {   // `log.warn()`
    return (this._level <= WARN);
  } else if (this._level > WARN) {
    return;
  } else if (arguments[0] instanceof Error) {
    // `log.warn(err, ...)`
    fields = {err: errSerializer(arguments[0])};
    if (arguments.length === 1) {
      msgArgs = [fields.err.message];
    } else {
      msgArgs = Array.prototype.slice.call(arguments, 1);
    }
  } else if (typeof (arguments[0]) === 'string') {  // `log.warn(msg, ...)`
    fields = null;
    msgArgs = Array.prototype.slice.call(arguments);
  } else {  // `log.warn(fields, msg, ...)`
    fields = arguments[0];
    msgArgs = Array.prototype.slice.call(arguments, 1);
  }
  var rec = this._mkRecord(fields, WARN, msgArgs);
  this._emit(rec);
}

/**
 * Log a record at ERROR level.
 *
 * Usages:
 *    log.error()  -> boolean is-error-enabled
 *    log.error(<Error> err, [<string> msg, ...])
 *    log.error(<string> msg, ...)
 *    log.error(<object> fields, <string> msg, ...)
 *
 * @params fields {Object} Optional set of additional fields to log.
 * @params msg {String} Log message. This can be followed by additional
 *    arguments that are handled like
 *    [util.format](http://nodejs.org/docs/latest/api/all.html#util.format).
 */
Logger.prototype.error = function () {
  var fields = null, msgArgs = null;
  if (arguments.length === 0) {   // `log.error()`
    return (this._level <= ERROR);
  } else if (this._level > ERROR) {
    return;
  } else if (arguments[0] instanceof Error) {
    // `log.error(err, ...)`
    fields = {err: errSerializer(arguments[0])};
    if (arguments.length === 1) {
      msgArgs = [fields.err.message];
    } else {
      msgArgs = Array.prototype.slice.call(arguments, 1);
    }
  } else if (typeof (arguments[0]) === 'string') {  // `log.error(msg, ...)`
    fields = null;
    msgArgs = Array.prototype.slice.call(arguments);
  } else {  // `log.error(fields, msg, ...)`
    fields = arguments[0];
    msgArgs = Array.prototype.slice.call(arguments, 1);
  }
  var rec = this._mkRecord(fields, ERROR, msgArgs);
  this._emit(rec);
}

/**
 * Log a record at FATAL level.
 *
 * Usages:
 *    log.fatal()  -> boolean is-fatal-enabled
 *    log.fatal(<Error> err, [<string> msg, ...])
 *    log.fatal(<string> msg, ...)
 *    log.fatal(<object> fields, <string> msg, ...)
 *
 * @params fields {Object} Optional set of additional fields to log.
 * @params msg {String} Log message. This can be followed by additional
 *    arguments that are handled like
 *    [util.format](http://nodejs.org/docs/latest/api/all.html#util.format).
 */
Logger.prototype.fatal = function () {
  var fields = null, msgArgs = null;
  if (arguments.length === 0) {   // `log.fatal()`
    return (this._level <= FATAL);
  } else if (this._level > FATAL) {
    return;
  } else if (arguments[0] instanceof Error) {
    // `log.fatal(err, ...)`
    fields = {err: errSerializer(arguments[0])};
    if (arguments.length === 1) {
      msgArgs = [fields.err.message];
    } else {
      msgArgs = Array.prototype.slice.call(arguments, 1);
    }
  } else if (typeof (arguments[0]) === 'string') {  // `log.fatal(msg, ...)`
    fields = null;
    msgArgs = Array.prototype.slice.call(arguments);
  } else {  // `log.fatal(fields, msg, ...)`
    fields = arguments[0];
    msgArgs = Array.prototype.slice.call(arguments, 1);
  }
  var rec = this._mkRecord(fields, FATAL, msgArgs);
  this._emit(rec);
}



//---- Standard serializers
// A serializer is a function that serializes a JavaScript object to a
// JSON representation for logging. There is a standard set of presumed
// interesting objects in node.js-land.

Logger.stdSerializers = {};

// Serialize an HTTP request.
Logger.stdSerializers.req = function req(req) {
  if (!req || !req.connection)
    return req;
  return {
    method: req.method,
    url: req.url,
    headers: req.headers,
    remoteAddress: req.connection.remoteAddress,
    remotePort: req.connection.remotePort
  };
  // Trailers: Skipping for speed. If you need trailers in your app, then
  // make a custom serializer.
  //if (Object.keys(trailers).length > 0) {
  //  obj.trailers = req.trailers;
  //}
};

// Serialize an HTTP response.
Logger.stdSerializers.res = function res(res) {
  if (!res || !res.statusCode)
    return res;
  return {
    statusCode: res.statusCode,
    header: res._header
  }
};

// Serialize an Error object
// (Core error properties are enumerable in node 0.4, not in 0.6).
var errSerializer = Logger.stdSerializers.err = function err(err) {
  if (!err || !err.stack)
    return err;
  var obj = {
    message: err.message,
    name: err.name,
    stack: err.stack
  }
  Object.keys(err).forEach(function (k) {
    if (err[k] !== undefined) {
      obj[k] = err[k];
    }
  });
  return obj;
};


/**
 * RingBuffer is a Writable Stream that just stores the last N records in
 * memory.
 *
 * @param options {Object}, with the following fields:
 *
 *    - limit: number of records to keep in memory
 */
function RingBuffer(options) {
  this.limit = options && options.limit ? options.limit : 100;
  this.writable = true;
  this.records = [];
  EventEmitter.call(this);
}

util.inherits(RingBuffer, EventEmitter);

RingBuffer.prototype.write = function (record) {
  if (!this.writable)
    throw (new Error('RingBuffer has been ended already'));

  this.records.push(record);

  if (this.records.length > this.limit)
    this.records.shift();

  return (true);
};

RingBuffer.prototype.end = function () {
  if (arguments.length > 0)
    this.write.apply(this, Array.prototype.slice.call(arguments));
  this.writable = false;
};

RingBuffer.prototype.destroy = function () {
  this.writable = false;
  this.emit('close');
};

RingBuffer.prototype.destroySoon = function () {
  this.destroy();
};


//---- Exports

module.exports = Logger;

module.exports.TRACE = TRACE;
module.exports.DEBUG = DEBUG;
module.exports.INFO = INFO;
module.exports.WARN = WARN;
module.exports.ERROR = ERROR;
module.exports.FATAL = FATAL;

module.exports.VERSION = VERSION;
module.exports.LOG_VERSION = LOG_VERSION;

module.exports.createLogger = function createLogger(options) {
  return new Logger(options);
};

module.exports.RingBuffer = RingBuffer;