Skip to content

Commit

Permalink
Add support for archiving old log files
Browse files Browse the repository at this point in the history
When working with file transport, add support for archiving (zip) the
previous files.
  • Loading branch information
nimrod-becker authored and indexzero committed Apr 21, 2015
1 parent b6ec262 commit 5868b78
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 8 deletions.
49 changes: 41 additions & 8 deletions lib/winston/transports/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -100,6 +102,7 @@ var File = exports.File = function (options) {
this._draining = false;
this._opening = false;
this._failures = 0;
this._archive = null;
};

//
Expand Down Expand Up @@ -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.
//
Expand Down Expand Up @@ -496,22 +501,35 @@ 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,
// but for sensible limits (10s - 100s of MB) this seems unlikely in less
// 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) {
if (err) {
if (err.code !== 'ENOENT') {
return self.emit('error', err);
}

return createAndFlush(0);
}

Expand All @@ -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),
Expand Down Expand Up @@ -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);
};

Expand All @@ -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),
Expand Down
83 changes: 83 additions & 0 deletions test/transports/file-archive-test.js
Original file line number Diff line number Diff line change
@@ -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);

0 comments on commit 5868b78

Please sign in to comment.