Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Winston log integration and log rotation features #131

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ There are several options that you should be aware of when using forever. Most o
'logFile': 'path/to/file', // Path to log output from forever process (when daemonized)
'outFile': 'path/to/file', // Path to log output from child stdout
'errFile': 'path/to/file', // Path to log output from child stderr

//
// Log rotation options, taken from winston
//
'tailable': false, // If true, log files will be rolled based on maxsize and maxfiles, but in ascending order. The filename will always have the most recent log lines. The larger the appended number, the older the log file. This option requires maxFiles to be set, or it will be ignored.
'maxsize': 10240, // Max size in bytes of the logfile, if the size is exceeded then a new file is created, a counter will become a suffix of the log file.
'maxFiles': 3, // Limit the number of files created when the size of the logfile is exceeded.
'zippedArchive': false, // If true, all log files but the current one will be zipped.

//
// ### function parseCommand (command, args)
Expand Down
42 changes: 22 additions & 20 deletions lib/forever-monitor/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,26 +43,28 @@ var Monitor = exports.Monitor = function (script, options) {
//
// Setup basic configuration options
//
options = options || {};
this.silent = options.silent || false;
this.killTree = options.killTree !== false;
this.uid = options.uid || utile.randomString(4);
this.id = options.id || false;
this.pidFile = options.pidFile;
this.max = options.max;
this.killTTL = options.killTTL;
this.killSignal = options.killSignal || 'SIGKILL';
this.childExists = false;
this.checkFile = options.checkFile !== false;
this.times = 0;
this.warn = console.error;

this.logFile = options.logFile;
this.outFile = options.outFile;
this.errFile = options.errFile;
this.append = options.append;
this.usePolling = options.usePolling;
this.pollingInterval = options.pollingInterval;
options = options || {};
this.silent = options.silent || false;
this.killTree = options.killTree !== false;
this.uid = options.uid || utile.randomString(4);
this.id = options.id || false;
this.pidFile = options.pidFile;
this.max = options.max;
this.killTTL = options.killTTL;
this.killSignal = options.killSignal || 'SIGKILL';
this.childExists = false;
this.checkFile = options.checkFile !== false;
this.times = 0;
this.warn = console.error;

this.logFile = options.logFile;
this.outFile = options.outFile;
this.errFile = options.errFile;
this.append = options.append;
this.tailable = options.tailable || false;
this.maxsize = options.maxsize || 10240;
this.maxFiles = options.maxFiles || 3;
this.zippedArchive = options.zippedArchive || false;

//
// Define some safety checks for commands with spaces
Expand Down
86 changes: 69 additions & 17 deletions lib/forever-monitor/plugins/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,50 @@
*
*/

var fs = require('fs');
var fs = require('fs'),
winston = require('winston'),
winstonStream = require('winston-stream');

//
// Name the plugin
//
exports.name = 'logger';

//
// ### @private function (file, level, options)
// #### @file {string} log filename
// #### @options {Object} Options for attaching to `Monitor`
// Helper function that sets up a winston logger to the specified file.
// We use the options to see if a logrotation is needed
//
function getWinstonLogger(file, options) {

var trasnportOptions = {
name: 'log',
filename: file,
options: {flags: options.append ? 'a+' : 'w+', encoding: 'utf8', mode: 0644},
json: false,
timestamp: true
};

if(options.tailable){
// LOG ROTATION OPTIONS
// log rotation for the logFile will not work if the spawner of this process (forever)
// has provided a file descriptor for stdout and stderr
// those values must be setted to 'ignore'
trasnportOptions.tailable = options.tailable;
trasnportOptions.maxsize = options.maxsize;
trasnportOptions.maxFiles = options.maxFiles;
trasnportOptions.zippedArchive = options.zippedArchive;
}

return new (winston.Logger)({
transports: [
new (winston.transports.File)(trasnportOptions)
]
});
}

//
// ### function attach (options)
// #### @options {Object} Options for attaching to `Monitor`
Expand All @@ -24,21 +61,20 @@ exports.attach = function (options) {
var monitor = this;

if (options.outFile) {
monitor.stdout = options.stdout || fs.createWriteStream(options.outFile, {
flags: monitor.append ? 'a+' : 'w+',
encoding: 'utf8',
mode: 0644
});
monitor.stdout = options.stdout || winstonStream(getWinstonLogger(options.outFile, options), "info");
}

if (options.errFile) {
monitor.stderr = options.stderr || fs.createWriteStream(options.errFile, {
flags: monitor.append ? 'a+' : 'w+',
encoding: 'utf8',
mode: 0644
});
monitor.stderr = options.stderr || winstonStream(getWinstonLogger(options.errFile, options), "error");
}


if (options.logFile) {
// Create the main forever logFile logger
var logFileStream = getWinstonLogger(options.logFile, options);
monitor.stdlogout = options.stdlogout || winstonStream(logFileStream, "info");
monitor.stdlogerr = options.stdlogerr || winstonStream(logFileStream, "error");
}

monitor.on('start', startLogs);
monitor.on('restart', startLogs);
monitor.on('exit', stopLogs);
Expand All @@ -49,17 +85,28 @@ exports.attach = function (options) {
// Remark: 0.8.x doesnt have an unpipe method
//
monitor.child.stdout.unpipe && monitor.child.stdout.unpipe(monitor.stdout);
monitor.stdout.destroy();
monitor.stdout._logger.remove("log");
monitor.stdout = null;
}
//
// Remark: 0.8.x doesnt have an unpipe method
//

if (monitor.stderr) {
//
// Remark: 0.8.x doesnt have an unpipe method
//
monitor.child.stderr.unpipe && monitor.child.stderr.unpipe(monitor.stderr);
monitor.stderr.destroy();
monitor.stderr._logger.remove("log");
monitor.stderr = null;
}

if (monitor.stdlogout && monitor.stdlogerr) {
monitor.child.stdout.unpipe && monitor.child.stdout.unpipe(monitor.stdlogout);
monitor.child.stderr.unpipe && monitor.child.stderr.unpipe(monitor.stdlogerr);

logFileStream.remove("log");

monitor.stdlogout = null;
monitor.stdlogerr = null;
}
}

function startLogs(child, childData) {
Expand All @@ -86,6 +133,11 @@ exports.attach = function (options) {
if (monitor.stderr) {
monitor.child.stderr.pipe(monitor.stderr, { end: false });
}

if (monitor.stdlogout && monitor.stdlogerr) {
monitor.child.stdout.pipe(monitor.stdlogout, { end: false });
monitor.child.stderr.pipe(monitor.stdlogerr, { end: false });
}
}
}
};
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
"chokidar": "^1.0.1",
"minimatch": "~2.0.0",
"ps-tree": "0.0.x",
"utile": "~0.2.1"
"utile": "~0.2.1",
"winston": "^2.2.0",
"winston-stream": "0.0.0"
},
"devDependencies": {
"optimist": "~0.6.1",
Expand Down
6 changes: 6 additions & 0 deletions test/fixtures/logs.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ for (var i = 0; i < 10; i++) {
console.log('stdout %d', i);
console.error('stderr %d', i);
}

// With the Winston logs integration I've noticed that forever-monitor can't log all the info of this script.
// Ocassionally the stdout file didn't contain all ten records. After some investigation I figured out that the logger sometimes can't write
// all the information beacause the life of the script and the monitor process ends While the logger has still data to write.
// By putting a delay the logs are tracked correctly
setTimeout(function(){}, 3000);
25 changes: 24 additions & 1 deletion test/plugins/logger-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function checkLogOutput(file, stream, expectedLength) {

assert.equal(lines.length, expectedLength);
lines.forEach(function (line, i) {
assert.equal(lines[i], stream + ' ' + (i % 10));
assert.equal(true, lines[i].indexOf(stream + ' ' + (i % 10)) !== -1);
});
}

Expand Down Expand Up @@ -79,6 +79,29 @@ vows.describe('forever-monitor/plugins/logger').addBatch({
checkLogOutput('logs-stdout.log', 'stdout', 40);
checkLogOutput('logs-stderr.log', 'stderr', 40);
}
},
'with log rotation enabled': {
topic: function () {
var monitor = new fmonitor.Monitor(path.join(fixturesDir, 'logs.js'), {
max: 2,
silent: true,
logFile: path.join(fixturesDir, 'logsr.log'),
outFile: path.join(fixturesDir, 'logsr-stdout.log'),
errFile: path.join(fixturesDir, 'logsr-stderr.log'),
tailable: true,
maxsize: 50,
maxFiles: 1,
zippedArchive: true
});

monitor.on('exit', this.callback.bind({}, null));
monitor.start();
},
'log files should be rotated': function (err) {
assert.equal(true, fs.existsSync(path.join(fixturesDir, 'logsr1.log.gz')));
assert.equal(true, fs.existsSync(path.join(fixturesDir, 'logsr-stdout1.log.gz')));
assert.equal(true, fs.existsSync(path.join(fixturesDir, 'logsr-stderr1.log.gz')));
}
}
}
}).export(module);
Expand Down