Jump To …
winston/common.html winston/config/cli-config.html winston/config/npm-config.html winston/config/syslog-config.html winston/config.html winston/exception.html winston/logger.html winston/transports/console.html winston/transports/couchdb.html winston/transports/file.html winston/transports/loggly.html winston/transports/transport.html winston/transports/webhook.html winston/transports.html winston.html

# file.js |
--- | ---
¶ |
    /*
     * file.js: Transport for outputting to a local log file
     *
     * (C) 2010 Charlie Robbins
     * MIT LICENCE
     *
     */

    var events = require('events'),
        fs = require('fs'),
        path = require('path'),
        util = require('util'),
        colors = require('colors'),
        common = require('../common'),
        Transport = require('./transport').Transport;
¶ ### function File (options) #### @options {Object} Options for this instance. Constructor function for the File transport object responsible for persisting log messages and metadata to one or more files. |
    var File = exports.File = function (options) {
      Transport.call(this, options);
¶ Helper function which throws an `Error` in the event that any of the rest of the arguments is present in `options`. |
      function throwIf (target /*, illegal... */) {
        Array.prototype.slice.call(arguments, 1).forEach(function (name) {
          if (options[name]) {
            throw new Error('Cannot set ' + name + ' and ' + target + 'together');
          }
        });
      }

      if (options.filename || options.dirname) {
        throwIf('filename or dirname', 'stream');
        this._basename = this.filename = path.basename(options.filename) || 'winston.log';
        this.dirname   = options.dirname || path.dirname(options.filename);
        this.options   = options.options || { flags: 'a' };
      }
      else if (options.stream) {
        throwIf('stream', 'filename', 'maxsize');
        this.stream = options.stream;
      }
      else {
        throw new Error('Cannot log to file without filename or stream.');
      }

      this.json      = options.json !== false;
      this.colorize  = options.colorize  || false;
      this.maxsize   = options.maxsize   || null;
      this.timestamp = typeof options.timestamp !== 'undefined' ? options.timestamp : false;
¶ Internal state variables representing the number of files this instance has created and the current size (in bytes) of the current logfile. |
      this._size    = 0;
      this._created = 0;
      this._buffer  = [];
    };
¶ Inherit from `winston.Transport`. |
    util.inherits(File, Transport);
¶ Expose the name of this Transport on the prototype |
    File.prototype.name = 'file';
¶ ### function log (level, msg, [meta], callback) #### @level {string} Level at which to log the message. #### @msg {string} Message to log #### @meta {Object} Optional Additional metadata to attach #### @callback {function} Continuation to respond to when complete. Core logging method exposed to Winston. Metadata is optional. |
    File.prototype.log = function (level, msg, meta, callback) {
      if (this.silent) {
        return callback(null, true);
      }

      var self = this, output = common.log({
        level:     level,
        message:   msg,
        meta:      meta,
        json:      this.json,
        colorize:  this.colorize,
        timestamp: this.timestamp
      }) + '\n';

      this._size += output.length;

      function onDrain () {
        self.emit('logged');
      }

      if (!this.filename) {
¶ If there is no `filename` on this instance then it was configured with a raw `WriteableStream` instance and we should not perform any size restrictions. |
        this.stream.write(output);
        this.stream.once('drain', onDrain);
      }
      else {
        this.open(function (err) {
          if (err) {
¶ If there was an error enqueue the message |
            return self._buffer.push(output);
          }

          self.stream.write(output);
          self.stream.once('drain', onDrain);
        });
      }

      callback(null, true);
    };
¶ ### function open (callback) #### @callback {function} Continuation to respond to when complete Checks to see if a new file needs to be created based on the `maxsize` (if any) and the current size of the file used. |
    File.prototype.open = function (callback) {
      if (this.opening) {
¶ If we are already attempting to open the next available file then respond with a value indicating that the message should be buffered. |
        return callback(true);
      }
      else if (!this.stream || (this.maxsize && this._size >= this.maxsize)) {
¶ If we dont have a stream or have exceeded our size, then create the next stream and respond with a value indicating that the message should be buffered. |
        callback(true);
        return this._createStream();
      }
¶ Otherwise we have a valid (and ready) stream. |
      callback();
    };
¶ ### function close () Closes the stream associated with this instance. |
    File.prototype.close = function() {
      var self = this;

      if (this.stream) {
        this.stream.end();
        this.stream.destroySoon();

        this.stream.once('drain', function () {
          self.emit('flush');
          self.emit('closed');
        });
      }
    };
¶ ### function flush () Flushes any buffered messages to the current `stream` used by this instance. |
    File.prototype.flush = function () {
      var self = this;
¶ Iterate over the `_buffer` of enqueued messaged and then write them to the newly created stream. |
      this._buffer.forEach(function (str) {
        process.nextTick(function () {
          self.stream.write(str);
          self._size += str.length;
        });
      });
¶ Quickly truncate the `_buffer` once the write operations have been started |
      self._buffer.length = 0;
¶ When the stream has drained we have flushed our buffer. |
      self.stream.once('drain', function () {
        self.emit('flush');
        self.emit('logged');
      });
    };
¶ ### @private function _createStream () Attempts to open the next appropriate file for this instance based on the common state (such as `maxsize` and `_basename`). |
    File.prototype._createStream = function () {
      var self = this;
      this.opening = true;

      (function checkFile (target) {
        var fullname = path.join(self.dirname, target);
¶ Creates the `WriteStream` and then flushes any buffered messages. |
        function createAndFlush (size) {
          if (self.stream) {
            self.stream.end();
            self.stream.destroySoon();
          }

          self._size = size;
          self.filename = target;
          self.stream = fs.createWriteStream(fullname, self.options);
¶ When the current stream has finished flushing then we can be sure we have finished opening and thus can emit the `open` event. |
          self.once('flush', function () {
            self.opening = false;
            self.emit('open', fullname);
          });
¶ Remark: It is possible that in the time it has taken to find the next logfile to be written more data than `maxsize` has been buffered, but for sensible limits (10s - 100s of MB) this seems unlikely in less than one second. |
          self.flush();
        }

        fs.stat(fullname, function (err, stats) {
          if (err) {
            if (err.code !== 'ENOENT') {
              return self.emit('error', err);
            }

            return createAndFlush(0);
          }

          if (!stats || (self.maxsize && stats.size >= self.maxsize)) {
¶ If `stats.size` is greater than the `maxsize` for this instance then try again |
            return checkFile(self._getFile(true));
          }

          createAndFlush(stats.size);
        });
      })(this._getFile());
    };
¶ ### @private function _getFile () Gets the next filename to use for this instance in the case that log filesizes are being capped. |
    File.prototype._getFile = function (inc) {
      var self = this,
          ext = path.extname(this._basename),
          basename = path.basename(this._basename, ext);

      if (inc) {
¶ Increment the number of files created or checked by this instance. |
        this._created += 1;
      }

      return this._created
        ? basename + this._created + ext
        : basename + ext;
    };