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 | 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 | 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 | 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 completeChecks to see if a new file needs to be created based on the | 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 | File.prototype.flush = function () {
var self = this; |
Iterate over the | this._buffer.forEach(function (str) {
process.nextTick(function () {
self.stream.write(str);
self._size += str.length;
});
});
|
Quickly truncate the | 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 | File.prototype._createStream = function () {
var self = this;
this.opening = true;
(function checkFile (target) {
var fullname = path.join(self.dirname, target);
|
Creates the | 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 | 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 | 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 | 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;
};
|