2 * file.js: Transport for outputting to a local log file
4 * (C) 2010 Charlie Robbins
9 var events = require('events'),
11 path = require('path'),
12 util = require('util'),
13 colors = require('colors'),
14 common = require('../common'),
15 Transport = require('./transport').Transport;
18 // ### function File (options)
19 // #### @options {Object} Options for this instance.
20 // Constructor function for the File transport object responsible
21 // for persisting log messages and metadata to one or more files.
23 var File = exports.File = function (options) {
24 Transport.call(this, options);
27 // Helper function which throws an `Error` in the event
28 // that any of the rest of the arguments is present in `options`.
30 function throwIf (target /*, illegal... */) {
31 Array.prototype.slice.call(arguments, 1).forEach(function (name) {
33 throw new Error('Cannot set ' + name + ' and ' + target + 'together');
38 if (options.filename || options.dirname) {
39 throwIf('filename or dirname', 'stream');
40 this._basename = this.filename = path.basename(options.filename) || 'winston.log';
41 this.dirname = options.dirname || path.dirname(options.filename);
42 this.options = options.options || { flags: 'a' };
44 else if (options.stream) {
45 throwIf('stream', 'filename', 'maxsize');
46 this.stream = options.stream;
49 throw new Error('Cannot log to file without filename or stream.');
52 this.json = options.json !== false;
53 this.colorize = options.colorize || false;
54 this.maxsize = options.maxsize || null;
55 this.maxFiles = options.maxFiles || null;
56 this.timestamp = typeof options.timestamp !== 'undefined' ? options.timestamp : false;
59 // Internal state variables representing the number
60 // of files this instance has created and the current
61 // size (in bytes) of the current logfile.
66 this._draining = false;
70 // Inherit from `winston.Transport`.
72 util.inherits(File, Transport);
75 // Expose the name of this Transport on the prototype
77 File.prototype.name = 'file';
80 // ### function log (level, msg, [meta], callback)
81 // #### @level {string} Level at which to log the message.
82 // #### @msg {string} Message to log
83 // #### @meta {Object} **Optional** Additional metadata to attach
84 // #### @callback {function} Continuation to respond to when complete.
85 // Core logging method exposed to Winston. Metadata is optional.
87 File.prototype.log = function (level, msg, meta, callback) {
89 return callback(null, true);
92 var self = this, output = common.log({
97 colorize: this.colorize,
98 timestamp: this.timestamp
101 this._size += output.length;
103 if (!this.filename) {
105 // If there is no `filename` on this instance then it was configured
106 // with a raw `WriteableStream` instance and we should not perform any
107 // size restrictions.
109 this.stream.write(output);
113 this.open(function (err) {
116 // If there was an error enqueue the message
118 return self._buffer.push(output);
121 self.stream.write(output);
126 callback(null, true);
130 // ### function open (callback)
131 // #### @callback {function} Continuation to respond to when complete
132 // Checks to see if a new file needs to be created based on the `maxsize`
133 // (if any) and the current size of the file used.
135 File.prototype.open = function (callback) {
138 // If we are already attempting to open the next
139 // available file then respond with a value indicating
140 // that the message should be buffered.
142 return callback(true);
144 else if (!this.stream || (this.maxsize && this._size >= this.maxsize)) {
146 // If we dont have a stream or have exceeded our size, then create
147 // the next stream and respond with a value indicating that
148 // the message should be buffered.
151 return this._createStream();
155 // Otherwise we have a valid (and ready) stream.
161 // ### function close ()
162 // Closes the stream associated with this instance.
164 File.prototype.close = function () {
169 this.stream.destroySoon();
171 this.stream.once('drain', function () {
179 // ### function flush ()
180 // Flushes any buffered messages to the current `stream`
181 // used by this instance.
183 File.prototype.flush = function () {
187 // Iterate over the `_buffer` of enqueued messaged
188 // and then write them to the newly created stream.
190 this._buffer.forEach(function (str) {
191 process.nextTick(function () {
192 self.stream.write(str);
193 self._size += str.length;
198 // Quickly truncate the `_buffer` once the write operations
201 self._buffer.length = 0;
204 // When the stream has drained we have flushed
207 self.stream.once('drain', function () {
214 // ### @private function _createStream ()
215 // Attempts to open the next appropriate file for this instance
216 // based on the common state (such as `maxsize` and `_basename`).
218 File.prototype._createStream = function () {
222 (function checkFile (target) {
223 var fullname = path.join(self.dirname, target);
226 // Creates the `WriteStream` and then flushes any
227 // buffered messages.
229 function createAndFlush (size) {
232 self.stream.destroySoon();
236 self.filename = target;
237 self.stream = fs.createWriteStream(fullname, self.options);
240 // When the current stream has finished flushing
241 // then we can be sure we have finished opening
242 // and thus can emit the `open` event.
244 self.once('flush', function () {
245 self.opening = false;
246 self.emit('open', fullname);
250 // Remark: It is possible that in the time it has taken to find the
251 // next logfile to be written more data than `maxsize` has been buffered,
252 // but for sensible limits (10s - 100s of MB) this seems unlikely in less
258 fs.stat(fullname, function (err, stats) {
260 if (err.code !== 'ENOENT') {
261 return self.emit('error', err);
264 return createAndFlush(0);
267 if (!stats || (self.maxsize && stats.size >= self.maxsize)) {
269 // If `stats.size` is greater than the `maxsize` for
270 // this instance then try again
272 return checkFile(self._getFile(true));
275 createAndFlush(stats.size);
281 // ### @private function _getFile ()
282 // Gets the next filename to use for this instance
283 // in the case that log filesizes are being capped.
285 File.prototype._getFile = function (inc) {
287 ext = path.extname(this._basename),
288 basename = path.basename(this._basename, ext),
293 // Increment the number of files created or
294 // checked by this instance.
296 // Check for maxFiles option and delete file
297 if (this.maxFiles && (this._created >= (this.maxFiles - 1))) {
298 remaining = this._created - (this.maxFiles - 1);
299 if (remaining === 0) {
300 fs.unlinkSync(path.join(this.dirname, basename + ext));
303 fs.unlinkSync(path.join(this.dirname, basename + remaining + ext));
311 ? basename + this._created + ext
316 // ### @private function _lazyDrain ()
317 // Lazily attempts to emit the `logged` event when `this.stream` has
318 // drained. This is really just a simple mutex that only works because
319 // Node.js is single-threaded.
321 File.prototype._lazyDrain = function () {
324 if (!this._draining && this.stream) {
325 this._draining = true;
327 this.stream.once('drain', function () {
328 this._draining = false;