From 5868b78a8655544d6f2db685971128b31552939c Mon Sep 17 00:00:00 2001 From: nimrod-becker Date: Wed, 15 Apr 2015 11:26:44 +0300 Subject: [PATCH] Add support for archiving old log files When working with file transport, add support for archiving (zip) the previous files. --- lib/winston/transports/file.js | 49 +++++++++++++--- test/transports/file-archive-test.js | 83 ++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 test/transports/file-archive-test.js diff --git a/lib/winston/transports/file.js b/lib/winston/transports/file.js index 213a25a57..6473c6c6d 100644 --- a/lib/winston/transports/file.js +++ b/lib/winston/transports/file.js @@ -11,6 +11,7 @@ var events = require('events'), path = require('path'), util = require('util'), async = require('async'), + zlib = require('zlib'), common = require('../common'), Transport = require('./transport').Transport, isWritable = require('isstream').isWritable, @@ -75,6 +76,7 @@ var File = exports.File = function (options) { this.colorize = options.colorize || false; this.maxsize = options.maxsize || null; this.rotationFormat = options.rotationFormat || false; + this.zippedArchive = options.zippedArchive || false; this.maxFiles = options.maxFiles || null; this.prettyPrint = options.prettyPrint || false; this.label = options.label || null; @@ -100,6 +102,7 @@ var File = exports.File = function (options) { this._draining = false; this._opening = false; this._failures = 0; + this._archive = null; }; // @@ -371,6 +374,8 @@ File.prototype.open = function (callback) { return this._createStream(); } + this._archive = this.zippedArchive ? this._stream.path : null; + // // Otherwise we have a valid (and ready) stream. // @@ -496,7 +501,6 @@ File.prototype._createStream = 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, @@ -504,6 +508,21 @@ File.prototype._createStream = function () { // than one second. // self.flush(); + compressFile(); + } + + function compressFile() { + if (self._archive) { + var gzip = zlib.createGzip(); + + var inp = fs.createReadStream(String(self._archive)); + var out = fs.createWriteStream(self._archive + '.gz'); + + inp.pipe(gzip).pipe(out); + + fs.unlink(String(self._archive)); + self._archive = ''; + } } fs.stat(fullname, function (err, stats) { @@ -511,7 +530,6 @@ File.prototype._createStream = function () { if (err.code !== 'ENOENT') { return self.emit('error', err); } - return createAndFlush(0); } @@ -530,6 +548,7 @@ File.prototype._createStream = function () { })(this._getFile()); }; + File.prototype._incFile = function (callback) { var ext = path.extname(this._basename), basename = path.basename(this._basename, ext), @@ -570,15 +589,24 @@ File.prototype._getFile = function () { // checked by this instance. // File.prototype._checkMaxFilesIncrementing = function (ext, basename, callback) { - var oldest, target; + var oldest, target, + self = this; + + if (self.zippedArchive) { + self._archive = path.join(self.dirname, basename + + ((self._created === 1) ? '' : self._created-1) + + ext); + } + // Check for maxFiles option and delete file - if (!this.maxFiles || this._created < this.maxFiles) { + if (!self.maxFiles || self._created < self.maxFiles) { return callback(); } - oldest = this._created - this.maxFiles; - target = path.join(this.dirname, basename + (oldest !== 0 ? oldest : '') + ext); + oldest = self._created - self.maxFiles; + target = path.join(self.dirname, basename + (oldest !== 0 ? oldest : '') + ext + + (self.zippedArchive ? '.gz' : '')); fs.unlink(target, callback); }; @@ -600,18 +628,23 @@ File.prototype._checkMaxFilesTailable = function (ext, basename, callback) { for (var x = this.maxFiles - 1; x > 0; x--) { tasks.push(function (i) { return function (cb) { - var tmppath = path.join(self.dirname, basename + (i - 1) + ext); + var tmppath = path.join(self.dirname, basename + (i - 1) + ext + + (self.zippedArchive ? '.gz' : '')); fs.exists(tmppath, function (exists) { if (!exists) { return cb(null); } - fs.rename(tmppath, path.join(self.dirname, basename + i + ext), cb); + fs.rename(tmppath, path.join(self.dirname, basename + i + ext + + (self.zippedArchive ? '.gz' : '')), cb); }); }; }(x)); } + if (self.zippedArchive) { + self._archive = path.join(self.dirname, basename + 1 + ext); + } async.series(tasks, function (err) { fs.rename( path.join(self.dirname, basename + ext), diff --git a/test/transports/file-archive-test.js b/test/transports/file-archive-test.js new file mode 100644 index 000000000..d076ce0ea --- /dev/null +++ b/test/transports/file-archive-test.js @@ -0,0 +1,83 @@ +/* + * file-archive-test.js: Tests for instances of the File transport setting the archive option, + * + * (C) 2015 Nimrod Becker + * MIT LICENSE + * + */ + +var assert = require('assert'), + exec = require('child_process').exec, + fs = require('fs'), + path = require('path'), + vows = require('vows'), + winston = require('../../lib/winston'), + helpers = require('../helpers'); + +var archiveTransport = new winston.transports.File({ + timestamp: true, + json: false, + zippedArchive: true, + tailable: true, + filename: 'testarchive.log', + dirname: path.join(__dirname, '..', 'fixtures', 'logs'), + maxsize: 4096, + maxFiles: 3 +}); + +function data(ch) { + return new Array(1018).join(String.fromCharCode(65 + ch)); +} + +function logKbytes(kbytes, txt) { + // + // With no timestamp and at the info level, + // winston adds exactly 7 characters: + // [info](4)[ :](2)[\n](1) + // + for (var i = 0; i < kbytes; i++) { + archiveTransport.log('info', data(txt), null, function() {}); + } +} + +vows.describe('winston/transports/file/zippedArchive').addBatch({ + "An instance of the File Transport with tailable true": { + "when created archived files are rolled": { + topic: function() { + var that = this, + created = 0; + + archiveTransport.on('logged', function() { + if (++created === 6) { + return that.callback(); + } + + logKbytes(4, created); + }); + + logKbytes(4, created); + }, + "should be only 3 files called testarchive.log, testarchive1.log.gz and testarchive2.log.gz": function() { + //Give the archive a little time to settle + // setTimeout(function() { + for (var num = 0; num < 6; num++) { + var file = !num ? 'testarchive.log' : 'testarchive' + num + '.log.gz', + fullpath = path.join(__dirname, '..', 'fixtures', 'logs', file); + + // There should be no files with that name + if (num >= 3) { + assert.throws(function() { + fs.statSync(fullpath); + }, Error); + } else { + // The other files should exist + assert.doesNotThrow(function() { + fs.statSync(fullpath); + }, Error); + } + } + //},5000); + }, + } + }, +}).export(module);