monitor.js | |
---|---|
/*
* monitor.js: Core functionality for the Monitor object.
*
* (C) 2010 Charlie Robbins
* MIT LICENCE
*
*/
var sys = require('sys'),
fs = require('fs'),
path = require('path'),
events = require('events'),
spawn = require('child_process').spawn,
winston = require('winston'),
forever = require('../forever'); | |
function Monitor (script, options)@script {string} Location of the target script to run.@options {Object} Configuration for this instance.Creates a new instance of forever with specified params. | var Monitor = exports.Monitor = function (script, options) {
var self = this;
events.EventEmitter.call(this); |
Setup basic configuration options | options = options || {};
this.silent = options.silent || false;
this.forever = options.forever || false;
this.uid = options.uid || forever.randomString(24);
this.pidFile = options.pidFile || path.join(forever.config.get('pidPath'), this.uid + '.pid');
this.max = options.max;
this.childExists = false;
this.times = 0; |
Setup restart timing. These options control how quickly forever restarts a child process as well as when to kill a "spinning" process | this.minUptime = typeof options.minUptime !== 'number' ? 0 : options.minUptime;
this.spinSleepTime = options.spinSleepTime || null; |
Setup the command to spawn and the options to pass to that command. | this.command = options.command || 'node';
this.options = options.options || [];
this.spawnWith = options.spawnWith || {};
this.sourceDir = options.sourceDir;
this.cwd = options.cwd || null;
this.env = options.env || {}; |
Setup log files and logger for this instance. | this.logFile = options.logFile || path.join(forever.config.get('root'), this.uid + '.log');
this.outFile = options.outFile;
this.errFile = options.errFile;
this.logger = options.logger || new (winston.Logger)({
transports: [new winston.transports.Console({ silent: this.silent })]
}); |
Extend from the winston logger. | this.logger.extend(this);
if (Array.isArray(script)) {
this.command = script[0];
this.options = script.slice(1);
}
else {
this.options.unshift(script);
}
if (this.sourceDir) {
this.options[0] = path.join(this.sourceDir, this.options[0]);
} |
If we should log stdout, open a file buffer | if (this.outFile) {
this.stdout = fs.createWriteStream(this.outFile, { flags: 'a+', encoding: 'utf8', mode: 0666 });
} |
If we should log stderr, open a file buffer | if (this.errFile) {
this.stderr = fs.createWriteStream(this.errFile, { flags: 'a+', encoding: 'utf8', mode: 0666 });
} |
Last if any hooks have been passed in attach this instance to them | if (options.hooks && options.hooks.length > 0) {
options.hooks.forEach(function (hook) {
if (typeof hook === 'function') {
return hook(self);
}
hook.attach(self);
});
}
}; |
Inherit from events.EventEmitter | sys.inherits(Monitor, events.EventEmitter); |
function start ([restart])@restart {boolean} Value indicating whether this is a restart.Start the process that this instance is configured for | Monitor.prototype.start = function (restart) {
var self = this;
if (this.running && !restart) {
process.nextTick(function () {
self.emit('error', new Error('Cannot start process that is already running.'));
});
}
var child = this.trySpawn();
if (!child) {
process.nextTick(function () {
self.emit('error', new Error('Target script does not exist: ' + self.options[0]));
});
return this;
}
this.ctime = Date.now();
this.child = child;
this.running = true;
process.nextTick(function () {
self.emit(restart ? 'restart' : 'start', self, self.data);
}); |
Hook all stream data and process it | function listenTo (stream) {
function ldata (data) {
if (!self.silent && !self[stream]) { |
If we haven't been silenced, and we don't have a file stream to output to write to the process stdout stream | process.stdout.write(data);
}
else if (self[stream]) { |
If we have been given an output file for the stream, write to it | self[stream].write(data);
}
self.emit(stream, data);
}
child[stream].on('data', ldata);
child.on('exit', function () {
child[stream].removeListener('data', ldata);
});
} |
Listen to stdout and stderr | listenTo('stdout');
listenTo('stderr');
child.on('exit', function (code) {
var spinning = Date.now() - self.ctime < self.minUptime;
self.warn('Forever detected script exited with code: ' + code);
function letChildDie() {
self.running = false; |
If had to write to an stdout file, close it | if (self.stdout) {
self.stdout.end();
} |
If had to write to an stderr file, close it | if (self.stderr) {
self.stderr.end();
}
self.emit('exit', self, spinning);
}
function restartChild() {
process.nextTick(function () {
self.warn('Forever restarting script for ' + self.times + ' time');
self.start(true);
});
}
self.times++;
if (self.forceStop || (!self.forever && self.times >= self.max)
|| (spinning && typeof self.spinSleepTime !== 'number')) {
letChildDie();
}
else if (spinning) {
setTimeout(restartChild, self.spinSleepTime);
}
else {
restartChild();
}
});
return this;
}; |
function trySpawn()Tries to spawn the target Forever child process. Depending on configuration, it checks the first argument of the options to see if the file exists. This is useful is you are trying to execute a script with an env: e.g. node myfile.js | Monitor.prototype.trySpawn = function () {
if (this.command === 'node' || (this.checkFile && !this.childExists)) {
try {
var stats = fs.statSync(this.options[0]);
this.childExists = true;
}
catch (ex) {
return false;
}
}
this.spawnWith.cwd = this.cwd || this.spawnWith.cwd;
this.spawnWith.env = this._getEnv();
return spawn(this.command, this.options, this.spawnWith);
}; |
@data {Object}Responds with the appropriate information about
this | Monitor.prototype.__defineGetter__('data', function () {
var self = this;
if (!this.running) { |
TODO: Return settings from this forever instance with a state indicator that it is currently stopped. | return {};
}
var childData = {
ctime: this.ctime,
command: this.command,
file: this.options[0],
foreverPid: process.pid,
logFile: this.logFile,
options: this.options.slice(1),
pid: this.child.pid,
silent: this.silent,
uid: this.uid
};
['pidFile', 'outFile', 'errFile', 'env', 'cwd'].forEach(function (key) {
if (self[key]) {
childData[key] = self[key];
}
});
if (this.sourceDir) {
childData.sourceDir = this.sourceDir;
childData.file = childData.file.replace(this.sourceDir + '/', '');
}
this.childData = childData;
return this.childData; |
Setup the forever process to listen to SIGINT and SIGTERM events so that we can clean up the *.pid file Remark: This should work, but the fd gets screwed up with the daemon process. process.on('SIGINT', function () { process.exit(0); }); process.on('SIGTERM', function () { process.exit(0); }); process.on('exit', function () { fs.unlinkSync(childPath); }); | }); |
function restart ()Restarts the target script associated with this instance. | Monitor.prototype.restart = function () {
return this.kill(false);
}; |
function stop ()Stops the target script associated with this instance. Prevents it from auto-respawning | Monitor.prototype.stop = function () {
return this.kill(true);
}; |
function kill (forceStop)@forceStop {boolean} Value indicating whether short circuit forever auto-restart.Kills the ChildProcess object associated with this instance. | Monitor.prototype.kill = function (forceStop) {
var self = this;
if (!this.child || !this.running) {
process.nextTick(function () {
self.emit('error', new Error('Cannot stop process that is not running.'));
});
}
else { |
Set an instance variable here to indicate this
stoppage is forced so that when | if (forceStop) {
this.forceStop = true;
}
this.child.kill();
this.emit('stop', this.childData);
}
return this;
}; |
@private function _getEnv ()Returns the environment variables that should be passed along to the target process spawned by this instance. | Monitor.prototype._getEnv = function () {
var self = this,
extra = Object.keys(this.env),
merged = {};
if (extra.length === 0) {
return process.env;
}
function addKey (key, source) {
merged[key] = source[key]
} |
Mixin the key:value pairs from | Object.keys(process.env).forEach(function (k) { addKey(k, process.env) });
extra.forEach(function (k) { addKey(k, self.env) });
return merged;
};
|