Skip to content

Commit 44862ff

Browse files
committed
Zip archives if zippedArchive is true
* Zip archives during rotation if zippedArchive is true * Remove gzip stream which was never written to (see winstonjs#1128 (comment)) * ads tests checking the contents of zipped archives * ads tests for rolling files with tailable: false * Fixes previously failing test tests/tail.file.test.js
1 parent 319abf1 commit 44862ff

File tree

3 files changed

+255
-27
lines changed

3 files changed

+255
-27
lines changed

lib/winston/transports/file.js

+50-17
Original file line numberDiff line numberDiff line change
@@ -566,11 +566,6 @@ module.exports = class File extends TransportStream {
566566
});
567567

568568
debug('create stream ok', fullpath);
569-
if (this.zippedArchive) {
570-
const gzip = zlib.createGzip();
571-
gzip.pipe(dest);
572-
return gzip;
573-
}
574569

575570
return dest;
576571
}
@@ -585,12 +580,30 @@ module.exports = class File extends TransportStream {
585580
const ext = path.extname(this._basename);
586581
const basename = path.basename(this._basename, ext);
587582

588-
if (!this.tailable) {
589-
this._created += 1;
590-
this._checkMaxFilesIncrementing(ext, basename, callback);
591-
} else {
592-
this._checkMaxFilesTailable(ext, basename, callback);
583+
const tasks = [];
584+
585+
if (this.zippedArchive) {
586+
const num = this._created > 0 && !this.tailable ? this._created : '';
587+
588+
tasks.push(function (cb) {
589+
this._compressFile(
590+
path.join(this.dirname, `${basename}${num}${ext}`),
591+
path.join(this.dirname, `${basename}${num}${ext}.gz`),
592+
cb
593+
);
594+
}.bind(this));
593595
}
596+
597+
tasks.push(function (cb) {
598+
if (!this.tailable) {
599+
this._created += 1;
600+
this._checkMaxFilesIncrementing(ext, basename, cb);
601+
} else {
602+
this._checkMaxFilesTailable(ext, basename, cb);
603+
}
604+
}.bind(this));
605+
606+
asyncSeries(tasks, callback);
594607
}
595608

596609
/**
@@ -609,13 +622,9 @@ module.exports = class File extends TransportStream {
609622
// Caveat emptor (indexzero): rotationFormat() was broken by design When
610623
// combined with max files because the set of files to unlink is never
611624
// stored.
612-
const target = !this.tailable && this._created
625+
return !this.tailable && this._created
613626
? `${basename}${isRotation}${ext}`
614627
: `${basename}${ext}`;
615-
616-
return this.zippedArchive && !this.tailable
617-
? `${target}.gz`
618-
: target;
619628
}
620629

621630
/**
@@ -658,7 +667,6 @@ module.exports = class File extends TransportStream {
658667
return;
659668
}
660669

661-
// const isZipped = this.zippedArchive ? '.gz' : '';
662670
const isZipped = this.zippedArchive ? '.gz' : '';
663671
for (let x = this.maxFiles - 1; x > 1; x--) {
664672
tasks.push(function (i, cb) {
@@ -678,13 +686,38 @@ module.exports = class File extends TransportStream {
678686

679687
asyncSeries(tasks, () => {
680688
fs.rename(
681-
path.join(this.dirname, `${basename}${ext}`),
689+
path.join(this.dirname, `${basename}${ext}${isZipped}`),
682690
path.join(this.dirname, `${basename}1${ext}${isZipped}`),
683691
callback
684692
);
685693
});
686694
}
687695

696+
/**
697+
* Compresses src to dest with gzip and unlinks src
698+
* @param {string} src - path to source file.
699+
* @param {string} dest - path to zipped destination file.
700+
* @param {Function} callback - callback called after file has been compressed.
701+
* @returns {undefined}
702+
* @private
703+
*/
704+
_compressFile(src, dest, callback) {
705+
fs.exists(src, exists => {
706+
if (!exists) return callback();
707+
708+
var gzip = zlib.createGzip();
709+
710+
var inp = fs.createReadStream(src);
711+
var out = fs.createWriteStream(dest);
712+
713+
out.on('finish', () => {
714+
fs.unlink(src, callback);
715+
});
716+
717+
inp.pipe(gzip).pipe(out);
718+
});
719+
}
720+
688721
_createLogDirIfNotExist(dirPath) {
689722
/* eslint-disable no-sync */
690723
if (!fs.existsSync(dirPath)) {

test/transports/file-archive.test.js

+113-10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
/* eslint-disable no-sync */
10+
const zlib = require('zlib');
1011
const assert = require('assert');
1112
const rimraf = require('rimraf');
1213
const fs = require('fs');
@@ -25,6 +26,16 @@ function removeFixtures(done) {
2526

2627
let archiveTransport = null;
2728

29+
function data(ch, kb) {
30+
return String.fromCharCode(65 + ch).repeat(kb * 1024 - 1);
31+
}
32+
33+
function logKbytes(kbytes, txt) {
34+
const toLog = {};
35+
toLog[MESSAGE] = data(txt, kbytes);
36+
archiveTransport.log(toLog);
37+
}
38+
2839
describe('winston/transports/file/zippedArchive', function () {
2940
describe('An instance of the File Transport with tailable true', function () {
3041
before(removeFixtures);
@@ -47,16 +58,6 @@ describe('winston/transports/file/zippedArchive', function () {
4758
let created = 0;
4859
let loggedTotal = 0;
4960

50-
function data(ch, kb) {
51-
return String.fromCharCode(65 + ch).repeat(kb * 1024 - 1);
52-
}
53-
54-
function logKbytes(kbytes, txt) {
55-
const toLog = {};
56-
toLog[MESSAGE] = data(txt, kbytes);
57-
archiveTransport.log(toLog);
58-
}
59-
6061
archiveTransport.on('logged', function (info) {
6162
loggedTotal += info[MESSAGE].length + 1;
6263
if (loggedTotal >= 14 * 1024) { // just over 3 x 4kb files
@@ -89,5 +90,107 @@ describe('winston/transports/file/zippedArchive', function () {
8990
}, Error);
9091
}
9192
});
93+
94+
it('testarchive.log should be ascii, testarchive1.log.gz and testarchive2.log.gz should be zipped', function () {
95+
const letters = ['D', 'C', 'B'];
96+
97+
for (var num = 0; num < 3; num++) {
98+
let content;
99+
const letter = letters[num];
100+
const file = !num ? 'testarchive.log' : 'testarchive' + num + '.log.gz';
101+
const fileContent = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'logs', file));
102+
103+
if (num > 0) {
104+
// archives should be zipped
105+
assert.doesNotThrow(function () {
106+
content = zlib.gunzipSync(fileContent).toString('ascii');
107+
});
108+
} else {
109+
// current log file should be plain text
110+
content = fileContent.toString('ascii');
111+
}
112+
113+
assert(content.match(new RegExp(letter, 'g'))[0].length, content.length);
114+
}
115+
});
116+
});
117+
118+
describe('An instance of the File Transport with tailable false', function () {
119+
before(removeFixtures);
120+
after(removeFixtures);
121+
122+
it('init logger AFTER cleaning up old files', function () {
123+
archiveTransport = new winston.transports.File({
124+
timestamp: true,
125+
json: false,
126+
zippedArchive: true,
127+
tailable: false,
128+
filename: 'testarchive.log',
129+
dirname: path.join(__dirname, '..', 'fixtures', 'logs'),
130+
maxsize: 4096,
131+
maxFiles: 3
132+
});
133+
});
134+
135+
it('when created archived files are rolled', function (done) {
136+
137+
let created = 0;
138+
let loggedTotal = 0;
139+
140+
archiveTransport.on('logged', function (info) {
141+
loggedTotal += info[MESSAGE].length + 1;
142+
if (loggedTotal >= 14 * 1024) { // just over 3 x 4kb files
143+
return done();
144+
}
145+
146+
if (loggedTotal % 4096 === 0) {
147+
created++;
148+
}
149+
// eslint-disable-next-line max-nested-callbacks
150+
setTimeout(() => logKbytes(1, created), 1);
151+
});
152+
153+
logKbytes(1, created);
154+
});
155+
156+
it('should be only 3 files called testarchive3.log, testarchive2.log.gz and testarchive1.log.gz', function () {
157+
for (var num = 0; num < 4; num++) {
158+
const file = num === 3 ? 'testarchive3.log' : 'testarchive' + (num > 0 ? num : '') + '.log.gz';
159+
const fullpath = path.join(__dirname, '..', 'fixtures', 'logs', file);
160+
161+
const statFile = function () {
162+
fs.statSync(fullpath);
163+
};
164+
165+
if (num === 0) {
166+
assert.throws(statFile, Error);
167+
} else {
168+
assert.doesNotThrow(statFile, Error);
169+
}
170+
}
171+
});
172+
173+
it('testarchive3.log should be ascii, testarchive2.log.gz and testarchive1.log.gz should be zipped', function () {
174+
const letters = ['B', 'C', 'D'];
175+
176+
for (var num = 1; num < 4; num++) {
177+
let content;
178+
const letter = letters[num - 1];
179+
const file = num === 3 ? 'testarchive3.log' : 'testarchive' + num + '.log.gz';
180+
const fileContent = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'logs', file));
181+
182+
if (num !== 3) {
183+
// archives should be zipped
184+
assert.doesNotThrow(function () {
185+
content = zlib.gunzipSync(fileContent).toString('ascii');
186+
});
187+
} else {
188+
// current log file should be plain text
189+
content = fileContent.toString('ascii');
190+
}
191+
192+
assert(content.match(new RegExp(letter, 'g'))[0].length, content.length);
193+
}
194+
});
92195
});
93196
});

test/transports/file-rolling.test.js

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/* eslint-disable no-sync */
2+
const assert = require('assert');
3+
const rimraf = require('rimraf');
4+
const fs = require('fs');
5+
const path = require('path');
6+
const winston = require('../../lib/winston');
7+
8+
const { MESSAGE } = require('triple-beam');
9+
10+
//
11+
// Remove all log fixtures
12+
//
13+
function removeFixtures(done) {
14+
rimraf(path.join(__dirname, '..', 'fixtures', 'logs', 'testrollingfiles*'), done);
15+
}
16+
17+
18+
19+
let rollTransport = null;
20+
21+
describe('winston/transports/file/rolling', function () {
22+
describe('An instance of the File Transport', function () {
23+
before(removeFixtures);
24+
after(removeFixtures);
25+
26+
it('init logger AFTER cleaning up old files', function () {
27+
rollTransport = new winston.transports.File({
28+
timestamp: false,
29+
json: false,
30+
filename: path.join(__dirname, '..', 'fixtures', 'logs', 'testrollingfiles.log'),
31+
maxsize: 4096,
32+
maxFiles: 3,
33+
tailable: false
34+
})
35+
});
36+
37+
it('and when passed more files than the maxFiles', function (done) {
38+
let created = 0;
39+
let loggedTotal = 0;
40+
41+
function data(ch, kb) {
42+
return String.fromCharCode(65 + ch).repeat(kb * 1024 - 1);
43+
}
44+
45+
function logKbytes(kbytes, txt) {
46+
const toLog = {};
47+
toLog[MESSAGE] = data(txt, kbytes);
48+
rollTransport.log(toLog);
49+
}
50+
51+
rollTransport.on('logged', function (info) {
52+
loggedTotal += info[MESSAGE].length + 1;
53+
if (loggedTotal >= 14 * 1024) { // just over 3 x 4kb files
54+
return done();
55+
}
56+
57+
if (loggedTotal % 4096 === 0) {
58+
created++;
59+
}
60+
// eslint-disable-next-line max-nested-callbacks
61+
setTimeout(() => logKbytes(1, created), 1);
62+
});
63+
64+
logKbytes(1, created);
65+
});
66+
67+
it('should be 3 log files, base to maxFiles - 1', function () {
68+
for (var num = 0; num < 4; num++) {
69+
const file = !num ? 'testrollingfiles.log' : 'testrollingfiles' + num + '.log';
70+
const fullpath = path.join(__dirname, '..', 'fixtures', 'logs', file);
71+
72+
if (num === 0) {
73+
return assert.ok(!fs.existsSync(fullpath));
74+
}
75+
76+
assert.ok(fs.existsSync(fullpath));
77+
}
78+
79+
return false;
80+
});
81+
82+
it('should have files in correct order', function () {
83+
['B', 'C', 'D'].forEach(function (letter, i) {
84+
const file = 'testrollingfiles' + (i + 1) + '.log';
85+
let content = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'logs', file), 'ascii');
86+
content = content.replace(/\s+/g, '');
87+
88+
assert(content.match(new RegExp(letter, 'g'))[0].length, content.length);
89+
});
90+
});
91+
});
92+
});

0 commit comments

Comments
 (0)