Skip to content

Commit 7d50c45

Browse files
author
Gareth Jones
committed
Rewrote file appender, fixing issue log4js-node#16 and issue log4js-node#31
1 parent 40c5f5e commit 7d50c45

File tree

3 files changed

+77
-98
lines changed

3 files changed

+77
-98
lines changed

lib/appenders/file.js

Lines changed: 59 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
var layouts = require('../layouts')
2+
, path = require('path')
23
, fs = require('fs');
34

45
/**
@@ -8,46 +9,69 @@ var layouts = require('../layouts')
89
* @param layout a function that takes a logevent and returns a string (defaults to basicLayout).
910
* @param logSize - the maximum size (in bytes) for a log file, if not provided then logs won't be rotated.
1011
* @param numBackups - the number of log files to keep after logSize has been reached (default 5)
11-
* @param filePollInterval - the time in seconds between file size checks (default 30s)
1212
*/
13-
function fileAppender (file, layout, logSize, numBackups, filePollInterval) {
13+
function fileAppender (file, layout, logSize, numBackups) {
14+
var bytesWritten = 0;
15+
file = path.normalize(file);
1416
layout = layout || layouts.basicLayout;
1517
numBackups = numBackups === undefined ? 5 : numBackups;
1618
//there has to be at least one backup if logSize has been specified
1719
numBackups = numBackups === 0 ? 1 : numBackups;
18-
filePollInterval = filePollInterval * 1000 || 30000;
1920

2021
function setupLogRolling () {
21-
fs.watchFile(
22-
file,
23-
{
24-
persistent: false,
25-
interval: filePollInterval
26-
},
27-
function (curr, prev) {
28-
if (curr.size >= logSize) {
29-
rollThatLog();
30-
}
22+
try {
23+
var stat = fs.statSync(file);
24+
bytesWritten = stat.size;
25+
if (bytesWritten >= logSize) {
26+
rollThatLog();
3127
}
32-
);
28+
} catch (e) {
29+
//file does not exist
30+
bytesWritten = 0;
31+
}
3332
}
3433

3534
function rollThatLog () {
36-
//roll the backups (rename file.n-1 to file.n, where n <= numBackups)
37-
for (var i=numBackups; i > 0; i--) {
38-
if (i > 1) {
39-
if (fileExists(file + '.' + (i-1))) {
40-
fs.renameSync(file+'.'+(i-1), file+'.'+i);
41-
}
35+
function index(filename) {
36+
return parseInt(filename.substring((path.basename(file) + '.').length), 10) || 0;
37+
}
38+
39+
var nameMatcher = new RegExp('^' + path.basename(file));
40+
function justTheLogFiles (item) {
41+
return nameMatcher.test(item);
42+
}
43+
44+
function byIndex(a, b) {
45+
if (index(a) > index(b)) {
46+
return 1;
47+
} else if (index(a) < index(b) ) {
48+
return -1;
4249
} else {
43-
fs.renameSync(file, file+'.1');
50+
return 0;
4451
}
4552
}
53+
54+
function increaseFileIndex (fileToRename) {
55+
var idx = index(fileToRename);
56+
if (idx < numBackups) {
57+
fs.renameSync(path.join(path.dirname(file), fileToRename), file + '.' + (idx + 1));
58+
}
59+
}
60+
61+
//roll the backups (rename file.n to file.n+1, where n <= numBackups)
62+
fs.readdirSync(path.dirname(file))
63+
.filter(justTheLogFiles)
64+
.sort(byIndex)
65+
.reverse()
66+
.forEach(increaseFileIndex);
67+
4668
//let's make a new file
4769
var newLogFileFD = fs.openSync(file, 'a', 0644)
4870
, oldLogFileFD = logFile.fd;
4971
logFile.fd = newLogFileFD;
5072
fs.close(oldLogFileFD);
73+
//reset the counter
74+
bytesWritten = 0;
5175
}
5276

5377
function fileExists (filename) {
@@ -62,26 +86,23 @@ function fileAppender (file, layout, logSize, numBackups, filePollInterval) {
6286
function openTheStream() {
6387
var stream = fs.createWriteStream(file, { flags: 'a', mode: 0644, encoding: 'utf8' });
6488
stream.on("open", function() {
65-
canWrite = true;
66-
while (logEventBuffer.length > 0 && canWrite) {
67-
canWrite = writeToLog(logEventBuffer.shift());
89+
if (logEventBuffer.length > 0) {
90+
writeToLog(logEventBuffer.shift());
6891
}
6992
});
7093
stream.on("error", function (err) {
71-
console.error("log4js.fileAppender - Error happened ", err);
94+
console.error("log4js.fileAppender - Writing to file %s, error happened ", file, err);
7295
});
7396
stream.on("drain", function() {
74-
canWrite = true;
75-
while (logEventBuffer.length > 0 && canWrite) {
76-
canWrite = writeToLog(logEventBuffer.shift());
97+
if (logEventBuffer.length > 0) {
98+
writeToLog(logEventBuffer.shift());
7799
}
78100
});
79101
return stream;
80102
}
81103

82104

83105
var logEventBuffer = []
84-
, canWrite = false
85106
, logFile = openTheStream();
86107

87108
if (logSize > 0) {
@@ -95,17 +116,17 @@ function fileAppender (file, layout, logSize, numBackups, filePollInterval) {
95116
});
96117

97118
function writeToLog(loggingEvent) {
98-
return logFile.write(layout(loggingEvent)+'\n', "utf8");
119+
var logMessage = layout(loggingEvent)+'\n';
120+
//not entirely accurate, but it'll do.
121+
bytesWritten += logMessage.length;
122+
logFile.write(logMessage, "utf8");
123+
if (bytesWritten >= logSize) {
124+
rollThatLog();
125+
}
99126
}
100127

101128
return function(loggingEvent) {
102-
//because the log stream is opened asynchronously, we don't want to write
103-
//until it is ready.
104-
if (canWrite) {
105-
canWrite = writeToLog(loggingEvent);
106-
} else {
107-
logEventBuffer.push(loggingEvent);
108-
}
129+
logEventBuffer.push(loggingEvent);
109130
};
110131
}
111132

@@ -114,7 +135,7 @@ function configure(config) {
114135
if (config.layout) {
115136
layout = layouts.layout(config.layout.type, config.layout);
116137
}
117-
return fileAppender(config.filename, layout, config.maxLogSize, config.backups, config.pollInterval);
138+
return fileAppender(config.filename, layout, config.maxLogSize, config.backups);
118139
}
119140

120141
exports.name = "file";

log-rolling.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,16 @@ var log4js = require('./lib/log4js')
33
, i = 0;
44
log4js.configure({
55
"appenders": [
6+
{
7+
type: "console"
8+
, category: "console"
9+
},
610
{
711
"type": "file",
812
"filename": "tmp-test.log",
913
"maxLogSize": 1024,
1014
"backups": 3,
11-
"pollInterval": 0.1,
1215
"category": "test"
13-
},
14-
{
15-
type: "console"
16-
, category: "console"
1716
}
1817
]
1918
});
@@ -23,6 +22,6 @@ function doTheLogging(x) {
2322
log.info("Logging something %d", x);
2423
}
2524

26-
for ( ; i < 100000; i++) {
25+
for ( ; i < 5000; i++) {
2726
doTheLogging(i);
2827
}

test/fileAppender.js

Lines changed: 13 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,15 @@ vows.describe('log4js fileAppender').addBatch({
4040
, that = this;
4141
remove(testFile);
4242
remove(testFile + '.1');
43-
//log file of 100 bytes maximum, no backups, check every 10ms for changes
44-
log4js.addAppender(log4js.fileAppender(testFile, log4js.layouts.basicLayout, 100, 0, 0.01), 'max-file-size');
43+
//log file of 100 bytes maximum, no backups
44+
log4js.addAppender(log4js.fileAppender(testFile, log4js.layouts.basicLayout, 100, 0), 'max-file-size');
4545
logger.info("This is the first log message.");
4646
logger.info("This is an intermediate log message.");
47-
//we have to wait before writing the second one, because node is too fast for the file system.
48-
setTimeout(function() {
49-
logger.info("This is the second log message.");
50-
}, 200);
47+
logger.info("This is the second log message.");
48+
//wait for the file system to catch up
5149
setTimeout(function() {
5250
fs.readFile(testFile, "utf8", that.callback);
53-
}, 400);
51+
}, 100);
5452
},
5553
'log file should only contain the second message': function(err, fileContents) {
5654
assert.include(fileContents, "This is the second log message.\n");
@@ -75,21 +73,17 @@ vows.describe('log4js fileAppender').addBatch({
7573
remove(testFile+'.1');
7674
remove(testFile+'.2');
7775

78-
//log file of 50 bytes maximum, 2 backups, check every 10ms for changes
79-
log4js.addAppender(log4js.fileAppender(testFile, log4js.layouts.basicLayout, 50, 2, 0.01), 'max-file-size-backups');
76+
//log file of 50 bytes maximum, 2 backups
77+
log4js.addAppender(log4js.fileAppender(testFile, log4js.layouts.basicLayout, 50, 2), 'max-file-size-backups');
8078
logger.info("This is the first log message.");
81-
//we have to wait before writing the second one, because node is too fast for the file system.
79+
logger.info("This is the second log message.");
80+
logger.info("This is the third log message.");
81+
logger.info("This is the fourth log message.");
8282
var that = this;
83+
//give the system a chance to open the stream
8384
setTimeout(function() {
84-
logger.info("This is the second log message.");
85-
}, 200);
86-
setTimeout(function() {
87-
logger.info("This is the third log message.");
88-
}, 400);
89-
setTimeout(function() {
90-
logger.info("This is the fourth log message.");
9185
fs.readdir(__dirname, that.callback);
92-
}, 600);
86+
}, 100);
9387
},
9488
'the log files': {
9589
topic: function(files) {
@@ -146,42 +140,7 @@ vows.describe('log4js fileAppender').addBatch({
146140
'should load appender configuration from a json file': function(err, contents) {
147141
assert.include(contents, 'this should be written to the file\n');
148142
assert.equal(contents.indexOf('this should not be written to the file'), -1);
149-
},
150-
'and log rolling': {
151-
topic: function() {
152-
var sandbox = require('sandboxed-module')
153-
, that = this
154-
, fileAppender = sandbox.require(
155-
'../lib/appenders/file.js'
156-
, { requires:
157-
{ 'fs':
158-
{
159-
watchFile: function(filename, options, cb) {
160-
that.callback(null, filename);
161-
}
162-
, createWriteStream: function() {
163-
return {
164-
on: function() {}
165-
, end: function() {}
166-
, destroy: function() {}
167-
};
168-
}
169-
}
170-
}
171-
}
172-
);
173-
174-
fileAppender.configure({
175-
filename: "cheese.log"
176-
, maxLogSize: 100
177-
, backups: 5
178-
, pollInterval: 30
179-
});
180-
},
181-
'should watch the log file': function(watchedFile) {
182-
assert.equal(watchedFile, 'cheese.log');
183-
}
184-
}
143+
}
185144
}
186145
}
187146

0 commit comments

Comments
 (0)