mirror of
https://github.com/mgerb/mywebsite
synced 2026-01-12 10:52:47 +00:00
1044 lines
28 KiB
JavaScript
1044 lines
28 KiB
JavaScript
/*
|
|
* forever.js: Top level include for the forever module
|
|
*
|
|
* (C) 2010 Charlie Robbins & the Contributors
|
|
* MIT LICENCE
|
|
*
|
|
*/
|
|
|
|
var fs = require('fs'),
|
|
path = require('path'),
|
|
events = require('events'),
|
|
exec = require('child_process').exec,
|
|
spawn = require('child_process').spawn,
|
|
cliff = require('cliff'),
|
|
nconf = require('nconf'),
|
|
nssocket = require('nssocket'),
|
|
timespan = require('timespan'),
|
|
utile = require('utile'),
|
|
winston = require('winston'),
|
|
mkdirp = utile.mkdirp,
|
|
async = utile.async;
|
|
|
|
var forever = exports;
|
|
|
|
//
|
|
// Setup `forever.log` to be a custom `winston` logger.
|
|
//
|
|
forever.log = new (winston.Logger)({
|
|
transports: [
|
|
new (winston.transports.Console)()
|
|
]
|
|
});
|
|
|
|
forever.log.cli();
|
|
|
|
//
|
|
// Setup `forever out` for logEvents with `winston` custom logger.
|
|
//
|
|
forever.out = new (winston.Logger)({
|
|
transports: [
|
|
new (winston.transports.Console)()
|
|
]
|
|
});
|
|
|
|
//
|
|
// ### Export Components / Settings
|
|
// Export `version` and important Prototypes from `lib/forever/*`
|
|
//
|
|
forever.initialized = false;
|
|
forever.kill = require('forever-monitor').kill;
|
|
forever.checkProcess = require('forever-monitor').checkProcess;
|
|
forever.root = process.env.FOREVER_ROOT || path.join(process.env.HOME || process.env.USERPROFILE || '/root', '.forever');
|
|
forever.config = new nconf.File({ file: path.join(forever.root, 'config.json') });
|
|
forever.Forever = forever.Monitor = require('forever-monitor').Monitor;
|
|
forever.Worker = require('./forever/worker').Worker;
|
|
forever.cli = require('./forever/cli');
|
|
|
|
//
|
|
// Expose version through `pkginfo`
|
|
//
|
|
exports.version = require('../package').version;
|
|
|
|
//
|
|
// ### function getSockets (sockPath, callback)
|
|
// #### @sockPath {string} Path in which to look for UNIX domain sockets
|
|
// #### @callback {function} Continuation to pass control to when complete
|
|
// Attempts to read the files from `sockPath` if the directory does not exist,
|
|
// then it is created using `mkdirp`.
|
|
//
|
|
function getSockets(sockPath, callback) {
|
|
var sockets;
|
|
|
|
try {
|
|
sockets = fs.readdirSync(sockPath);
|
|
}
|
|
catch (ex) {
|
|
if (ex.code !== 'ENOENT') {
|
|
return callback(ex);
|
|
}
|
|
|
|
return mkdirp(sockPath, '0755', function (err) {
|
|
return err ? callback(err) : callback(null, []);
|
|
});
|
|
}
|
|
|
|
callback(null, sockets);
|
|
}
|
|
|
|
//
|
|
// ### function getAllProcess (callback)
|
|
// #### @callback {function} Continuation to respond to when complete.
|
|
// Returns all data for processes managed by forever.
|
|
//
|
|
function getAllProcesses(callback) {
|
|
var sockPath = forever.config.get('sockPath');
|
|
|
|
function getProcess(name, next) {
|
|
var fullPath = path.join(sockPath, name),
|
|
socket = new nssocket.NsSocket();
|
|
|
|
if (process.platform === 'win32') {
|
|
// it needs the prefix
|
|
fullPath = '\\\\.\\pipe\\' + fullPath;
|
|
}
|
|
|
|
socket.connect(fullPath, function (err) {
|
|
if (err) {
|
|
next(err);
|
|
}
|
|
|
|
socket.dataOnce(['data'], function (data) {
|
|
data.socket = fullPath;
|
|
next(null, data);
|
|
socket.end();
|
|
});
|
|
|
|
socket.send(['data']);
|
|
});
|
|
|
|
socket.on('error', function (err) {
|
|
if (err.code === 'ECONNREFUSED') {
|
|
fs.unlink(fullPath, function () {
|
|
next();
|
|
});
|
|
}
|
|
else {
|
|
next();
|
|
}
|
|
});
|
|
}
|
|
|
|
getSockets(sockPath, function (err, sockets) {
|
|
if (err || (sockets && sockets.length === 0)) {
|
|
return callback(err);
|
|
}
|
|
|
|
async.map(sockets, getProcess, function (err, processes) {
|
|
callback(err, processes.filter(Boolean));
|
|
});
|
|
});
|
|
}
|
|
|
|
//
|
|
// ### function getAllPids ()
|
|
// Returns the set of all pids managed by forever.
|
|
// e.x. [{ pid: 12345, foreverPid: 12346 }, ...]
|
|
//
|
|
function getAllPids(processes) {
|
|
return !processes ? null : processes.map(function (proc) {
|
|
return {
|
|
pid: proc.pid,
|
|
foreverPid: proc.foreverPid
|
|
};
|
|
});
|
|
}
|
|
|
|
//
|
|
// ### function stopOrRestart (action, event, format, target)
|
|
// #### @action {string} Action that is to be sent to target(s).
|
|
// #### @event {string} Event that will be emitted on success.
|
|
// #### @format {boolean} Indicated if we should CLI format the returned output.
|
|
// #### @target {string} Index or script name to stop. Optional.
|
|
// #### If not provided -> action will be sent to all targets.
|
|
// Returns emitter that you can use to handle events on failure or success (i.e 'error' or <event>)
|
|
//
|
|
function stopOrRestart(action, event, format, target) {
|
|
var emitter = new events.EventEmitter();
|
|
|
|
function sendAction(proc, next) {
|
|
var socket = new nssocket.NsSocket();
|
|
|
|
function onMessage(data) {
|
|
//
|
|
// Cleanup the socket.
|
|
//
|
|
socket.undata([action, 'ok'], onMessage);
|
|
socket.undata([action, 'error'], onMessage);
|
|
socket.end();
|
|
|
|
//
|
|
// Messages are only sent back from error cases. The event
|
|
// calling context is available from `nssocket`.
|
|
//
|
|
var message = data && data.message,
|
|
type = this.event.slice().pop();
|
|
|
|
//
|
|
// Remark (Tjatse): This message comes from `forever-monitor`, the process is marked
|
|
// as `STOPPED`: message: Cannot stop process that is not running.
|
|
//
|
|
// Remark (indexzero): We should probably warn instead of emitting an error in `forever-monitor`,
|
|
// OR handle that error in `bin/worker` for better RPC.
|
|
//
|
|
return type === 'error' && /is not running/.test(message)
|
|
? next(new Error(message))
|
|
: next(null, data);
|
|
}
|
|
|
|
socket.connect(proc.socket, function (err) {
|
|
if (err) {
|
|
next(err);
|
|
}
|
|
|
|
socket.dataOnce([action, 'ok'], onMessage);
|
|
socket.dataOnce([action, 'error'], onMessage);
|
|
socket.send([action]);
|
|
});
|
|
|
|
//
|
|
// Remark (indexzero): This is a race condition, but unlikely to ever hit since
|
|
// if the socket errors we will never get any data in the first place...
|
|
//
|
|
socket.on('error', function (err) {
|
|
next(err);
|
|
});
|
|
}
|
|
|
|
|
|
getAllProcesses(function (err, processes) {
|
|
if (err) {
|
|
return process.nextTick(function () {
|
|
emitter.emit('error', err);
|
|
});
|
|
}
|
|
|
|
var procs;
|
|
if (target !== undefined && target !== null) {
|
|
if (isNaN(target)) {
|
|
procs = forever.findByScript(target, processes);
|
|
}
|
|
procs = procs
|
|
|| forever.findById(target, processes)
|
|
|| forever.findByIndex(target, processes)
|
|
|| forever.findByUid(target, processes)
|
|
|| forever.findByPid(target, processes);
|
|
}
|
|
else {
|
|
procs = processes;
|
|
}
|
|
|
|
if (procs && procs.length > 0) {
|
|
async.map(procs, sendAction, function (err, results) {
|
|
if (err) {
|
|
emitter.emit('error', err);
|
|
}
|
|
|
|
//
|
|
// Remark (indexzero): we should do something with the results.
|
|
//
|
|
emitter.emit(event, forever.format(format, procs));
|
|
});
|
|
}
|
|
else {
|
|
process.nextTick(function () {
|
|
emitter.emit('error', new Error('Cannot find forever process: ' + target));
|
|
});
|
|
}
|
|
});
|
|
|
|
return emitter;
|
|
}
|
|
|
|
//
|
|
// ### function load (options, [callback])
|
|
// #### @options {Object} Options to load into the forever module
|
|
// Initializes configuration for forever module
|
|
//
|
|
forever.load = function (options) {
|
|
// memorize current options.
|
|
this._loadedOptions = options;
|
|
|
|
//
|
|
// Setup the incoming options with default options.
|
|
//
|
|
options = options || {};
|
|
options.loglength = options.loglength || 100;
|
|
options.logstream = options.logstream || false;
|
|
options.root = options.root || forever.root;
|
|
options.pidPath = options.pidPath || path.join(options.root, 'pids');
|
|
options.sockPath = options.sockPath || path.join(options.root, 'sock');
|
|
|
|
//
|
|
// If forever is initalized and the config directories are identical
|
|
// simply return without creating directories
|
|
//
|
|
if (forever.initialized && forever.config.get('root') === options.root &&
|
|
forever.config.get('pidPath') === options.pidPath) {
|
|
return;
|
|
}
|
|
|
|
forever.config = new nconf.File({ file: path.join(options.root, 'config.json') });
|
|
|
|
//
|
|
// Try to load the forever `config.json` from
|
|
// the specified location.
|
|
//
|
|
try {
|
|
forever.config.loadSync();
|
|
}
|
|
catch (ex) { }
|
|
|
|
//
|
|
// Setup the columns for `forever list`.
|
|
//
|
|
options.columns = options.columns || forever.config.get('columns');
|
|
if (!options.columns) {
|
|
options.columns = [
|
|
'uid', 'command', 'script', 'forever', 'pid', 'id', 'logfile', 'uptime'
|
|
];
|
|
}
|
|
|
|
forever.config.set('root', options.root);
|
|
forever.config.set('pidPath', options.pidPath);
|
|
forever.config.set('sockPath', options.sockPath);
|
|
forever.config.set('loglength', options.loglength);
|
|
forever.config.set('logstream', options.logstream);
|
|
forever.config.set('columns', options.columns);
|
|
|
|
//
|
|
// Setup timestamp to event logger
|
|
//
|
|
forever.out.transports.console.timestamp = forever.config.get('timestamp') === 'true';
|
|
|
|
//
|
|
// Attempt to see if `forever` has been configured to
|
|
// run in debug mode.
|
|
//
|
|
options.debug = options.debug || forever.config.get('debug') || false;
|
|
|
|
if (options.debug) {
|
|
//
|
|
// If we have been indicated to debug this forever process
|
|
// then setup `forever._debug` to be an instance of `winston.Logger`.
|
|
//
|
|
forever._debug();
|
|
}
|
|
|
|
//
|
|
// Syncronously create the `root` directory
|
|
// and the `pid` directory for forever. Although there is
|
|
// an additional overhead here of the sync action. It simplifies
|
|
// the setup of forever dramatically.
|
|
//
|
|
function tryCreate(dir) {
|
|
try {
|
|
fs.mkdirSync(dir, '0755');
|
|
}
|
|
catch (ex) { }
|
|
}
|
|
|
|
tryCreate(forever.config.get('root'));
|
|
tryCreate(forever.config.get('pidPath'));
|
|
tryCreate(forever.config.get('sockPath'));
|
|
|
|
//
|
|
// Attempt to save the new `config.json` for forever
|
|
//
|
|
try {
|
|
forever.config.saveSync();
|
|
}
|
|
catch (ex) { }
|
|
|
|
forever.initialized = true;
|
|
};
|
|
|
|
//
|
|
// ### @private function _debug ()
|
|
// Sets up debugging for this forever process
|
|
//
|
|
forever._debug = function () {
|
|
var debug = forever.config.get('debug');
|
|
|
|
if (!debug) {
|
|
forever.config.set('debug', true);
|
|
forever.log.add(winston.transports.File, {
|
|
level: 'silly',
|
|
filename: path.join(forever.config.get('root'), 'forever.debug.log')
|
|
});
|
|
}
|
|
};
|
|
|
|
//
|
|
// Ensure forever will always be loaded the first time it is required.
|
|
//
|
|
forever.load();
|
|
|
|
//
|
|
// ### function stat (logFile, script, callback)
|
|
// #### @logFile {string} Path to the log file for this script
|
|
// #### @logAppend {boolean} Optional. True Prevent failure if the log file exists.
|
|
// #### @script {string} Path to the target script.
|
|
// #### @callback {function} Continuation to pass control back to
|
|
// Ensures that the logFile doesn't exist and that
|
|
// the target script does exist before executing callback.
|
|
//
|
|
forever.stat = function (logFile, script, callback) {
|
|
var logAppend;
|
|
|
|
if (arguments.length === 4) {
|
|
logAppend = callback;
|
|
callback = arguments[3];
|
|
}
|
|
|
|
fs.stat(script, function (err, stats) {
|
|
if (err) {
|
|
return callback(new Error('script ' + script + ' does not exist.'));
|
|
}
|
|
|
|
return logAppend ? callback(null) : fs.stat(logFile, function (err, stats) {
|
|
return !err
|
|
? callback(new Error('log file ' + logFile + ' exists. Use the -a or --append option to append log.'))
|
|
: callback(null);
|
|
});
|
|
});
|
|
};
|
|
|
|
//
|
|
// ### function start (script, options)
|
|
// #### @script {string} Location of the script to run.
|
|
// #### @options {Object} Configuration for forever instance.
|
|
// Starts a script with forever
|
|
//
|
|
forever.start = function (script, options) {
|
|
if (!options.uid) {
|
|
options.uid = utile.randomString(4).replace(/^\-/, '_');
|
|
}
|
|
|
|
if (!options.logFile) {
|
|
options.logFile = forever.logFilePath(options.uid + '.log');
|
|
}
|
|
|
|
//
|
|
// Create the monitor, log events, and start.
|
|
//
|
|
var monitor = new forever.Monitor(script, options);
|
|
forever.logEvents(monitor);
|
|
return monitor.start();
|
|
};
|
|
|
|
//
|
|
// ### function startDaemon (script, options)
|
|
// #### @script {string} Location of the script to run.
|
|
// #### @options {Object} Configuration for forever instance.
|
|
// Starts a script with forever as a daemon
|
|
//
|
|
forever.startDaemon = function (script, options) {
|
|
options = options || {};
|
|
options.uid = options.uid || utile.randomString(4).replace(/^\-/, '_');
|
|
options.logFile = forever.logFilePath(options.logFile || forever.config.get('logFile') || options.uid + '.log');
|
|
options.pidFile = forever.pidFilePath(options.pidFile || forever.config.get('pidFile') || options.uid + '.pid');
|
|
|
|
var monitor, outFD, errFD, monitorPath;
|
|
|
|
//
|
|
// This log file is forever's log file - the user's outFile and errFile
|
|
// options are not taken into account here. This will be an aggregate of all
|
|
// the app's output, as well as messages from the monitor process, where
|
|
// applicable.
|
|
//
|
|
outFD = fs.openSync(options.logFile, 'a');
|
|
errFD = fs.openSync(options.logFile, 'a');
|
|
monitorPath = path.resolve(__dirname, '..', 'bin', 'monitor');
|
|
|
|
monitor = spawn(process.execPath, [monitorPath, script], {
|
|
stdio: ['ipc', outFD, errFD],
|
|
detached: true
|
|
});
|
|
|
|
monitor.on('exit', function (code) {
|
|
console.error('Monitor died unexpectedly with exit code %d', code);
|
|
});
|
|
|
|
// transmit options to daemonic(child) process, keep configuration lineage.
|
|
options._loadedOptions = this._loadedOptions;
|
|
|
|
monitor.send(JSON.stringify(options));
|
|
|
|
// close the ipc communication channel with the monitor
|
|
// otherwise the corresponding events listeners will prevent
|
|
// the exit of the current process (observed with node 0.11.9)
|
|
monitor.disconnect();
|
|
|
|
// make sure the monitor is unref() and does not prevent the
|
|
// exit of the current process
|
|
monitor.unref();
|
|
|
|
return monitor;
|
|
};
|
|
|
|
//
|
|
// ### function startServer ()
|
|
// #### @arguments {forever.Monitor...} A list of forever.Monitor instances
|
|
// Starts the `forever` HTTP server for communication with the forever CLI.
|
|
// **NOTE:** This will change your `process.title`.
|
|
//
|
|
forever.startServer = function () {
|
|
var args = Array.prototype.slice.call(arguments),
|
|
monitors = [],
|
|
callback;
|
|
|
|
args.forEach(function (a) {
|
|
if (Array.isArray(a)) {
|
|
monitors = monitors.concat(a.filter(function (m) {
|
|
return m instanceof forever.Monitor;
|
|
}));
|
|
}
|
|
else if (a instanceof forever.Monitor) {
|
|
monitors.push(a);
|
|
}
|
|
else if (typeof a === 'function') {
|
|
callback = a;
|
|
}
|
|
});
|
|
|
|
async.map(monitors, function (monitor, next) {
|
|
var worker = new forever.Worker({
|
|
monitor: monitor,
|
|
sockPath: forever.config.get('sockPath'),
|
|
exitOnStop: true
|
|
});
|
|
|
|
worker.start(function (err) {
|
|
return err ? next(err) : next(null, worker);
|
|
});
|
|
}, callback || function () {});
|
|
};
|
|
|
|
|
|
//
|
|
// ### function stop (target, [format])
|
|
// #### @target {string} Index or script name to stop
|
|
// #### @format {boolean} Indicated if we should CLI format the returned output.
|
|
// Stops the process(es) with the specified index or script name
|
|
// in the list of all processes
|
|
//
|
|
forever.stop = function (target, format) {
|
|
return stopOrRestart('stop', 'stop', format, target);
|
|
};
|
|
|
|
//
|
|
// ### function restart (target, format)
|
|
// #### @target {string} Index or script name to restart
|
|
// #### @format {boolean} Indicated if we should CLI format the returned output.
|
|
// Restarts the process(es) with the specified index or script name
|
|
// in the list of all processes
|
|
//
|
|
forever.restart = function (target, format) {
|
|
return stopOrRestart('restart', 'restart', format, target);
|
|
};
|
|
|
|
//
|
|
// ### function stopbypid (target, format)
|
|
// #### @pid {string} Pid of process to stop.
|
|
// #### @format {boolean} Indicated if we should CLI format the returned output.
|
|
// Stops the process with specified pid
|
|
//
|
|
forever.stopbypid = function (pid, format) {
|
|
// stopByPid only capable of stopping, but can't restart
|
|
return stopOrRestart('stop', 'stopByPid', format, pid);
|
|
};
|
|
|
|
//
|
|
// ### function restartAll (format)
|
|
// #### @format {boolean} Value indicating if we should format output
|
|
// Restarts all processes managed by forever.
|
|
//
|
|
forever.restartAll = function (format) {
|
|
return stopOrRestart('restart', 'restartAll', format);
|
|
};
|
|
|
|
//
|
|
// ### function stopAll (format)
|
|
// #### @format {boolean} Value indicating if we should format output
|
|
// Stops all processes managed by forever.
|
|
//
|
|
forever.stopAll = function (format) {
|
|
return stopOrRestart('stop', 'stopAll', format);
|
|
};
|
|
|
|
//
|
|
// ### function list (format, procs, callback)
|
|
// #### @format {boolean} If set, will return a formatted string of data
|
|
// #### @callback {function} Continuation to respond to when complete.
|
|
// Returns the list of all process data managed by forever.
|
|
//
|
|
forever.list = function (format, callback) {
|
|
getAllProcesses(function (err, processes) {
|
|
callback(err, forever.format(format, processes));
|
|
});
|
|
};
|
|
|
|
//
|
|
// ### function tail (target, length, callback)
|
|
// #### @target {string} Target script to list logs for
|
|
// #### @options {length|stream} **Optional** Length of the logs to tail, boolean stream
|
|
// #### @callback {function} Continuation to respond to when complete.
|
|
// Responds with the latest `length` logs for the specified `target` process
|
|
// managed by forever. If no `length` is supplied then `forever.config.get('loglength`)`
|
|
// is used.
|
|
//
|
|
forever.tail = function (target, options, callback) {
|
|
if (!callback && typeof options === 'function') {
|
|
callback = options;
|
|
options.length = 0;
|
|
options.stream = false;
|
|
}
|
|
|
|
var that = this,
|
|
length = options.length || forever.config.get('loglength'),
|
|
stream = options.stream || forever.config.get('logstream'),
|
|
blanks = function (e, i, a) { return e !== ''; },
|
|
title = function (e, i, a) { return e.match(/^==>/); },
|
|
args = ['-n', length],
|
|
logs;
|
|
|
|
if (stream) { args.unshift('-f'); }
|
|
|
|
function tailProcess(procs, next) {
|
|
var count = 0,
|
|
map = {},
|
|
tail;
|
|
|
|
procs.forEach(function (proc) {
|
|
args.push(proc.logFile);
|
|
map[proc.logFile] = { pid: proc.pid, file: proc.file };
|
|
count++;
|
|
});
|
|
|
|
tail = spawn('tail', args, {
|
|
stdio: [null, 'pipe', 'pipe'],
|
|
});
|
|
|
|
tail.stdio[1].setEncoding('utf8');
|
|
tail.stdio[2].setEncoding('utf8');
|
|
|
|
tail.stdio[1].on('data', function (data) {
|
|
var chunk = data.split('\n\n');
|
|
chunk.forEach(function (logs) {
|
|
var logs = logs.split('\n').filter(blanks),
|
|
file = logs.filter(title),
|
|
lines,
|
|
proc;
|
|
|
|
proc = file.length
|
|
? map[file[0].split(' ')[1]]
|
|
: map[procs[0].logFile];
|
|
|
|
lines = count !== 1
|
|
? logs.slice(1)
|
|
: logs;
|
|
|
|
lines.forEach(function (line) {
|
|
callback(null, { file: proc.file, pid: proc.pid, line: line });
|
|
});
|
|
});
|
|
});
|
|
|
|
tail.stdio[2].on('data', function (err) {
|
|
return callback(err);
|
|
});
|
|
}
|
|
|
|
getAllProcesses(function (err, processes) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
else if (!processes) {
|
|
return callback(new Error('Cannot find forever process: ' + target));
|
|
}
|
|
|
|
var procs = forever.findByIndex(target, processes)
|
|
|| forever.findByScript(target, processes);
|
|
|
|
if (!procs) {
|
|
return callback(new Error('No logs available for process: ' + target));
|
|
}
|
|
|
|
tailProcess(procs, callback);
|
|
});
|
|
};
|
|
|
|
//
|
|
// ### function findById (id, processes)
|
|
// #### @index {string} Index of the process to find.
|
|
// #### @processes {Array} Set of processes to find in.
|
|
// Finds the process with the specified index.
|
|
//
|
|
forever.findById = function (id, processes) {
|
|
if (!processes) { return null; }
|
|
|
|
var procs = processes.filter(function (p) {
|
|
return p.id == id;
|
|
});
|
|
|
|
if (procs.length === 0) { procs = null; }
|
|
return procs;
|
|
};
|
|
|
|
//
|
|
// ### function findByIndex (index, processes)
|
|
// #### @index {string} Index of the process to find.
|
|
// #### @processes {Array} Set of processes to find in.
|
|
// Finds the process with the specified index.
|
|
//
|
|
forever.findByIndex = function (index, processes) {
|
|
var indexAsNum = parseInt(index, 10),
|
|
proc;
|
|
|
|
if (indexAsNum == index) {
|
|
proc = processes && processes[indexAsNum];
|
|
}
|
|
return proc ? [proc] : null;
|
|
};
|
|
|
|
//
|
|
// ### function findByScript (script, processes)
|
|
// #### @script {string} The name of the script to find.
|
|
// #### @processes {Array} Set of processes to find in.
|
|
// Finds the process with the specified script name.
|
|
//
|
|
forever.findByScript = function (script, processes) {
|
|
if (!processes) { return null; }
|
|
|
|
// make script absolute.
|
|
if (script.indexOf('/') != 0) {
|
|
script = path.resolve(process.cwd(), script);
|
|
}
|
|
|
|
var procs = processes.filter(function (p) {
|
|
return p.file === script || path.join(p.spawnWith.cwd, p.file) === script;
|
|
});
|
|
|
|
if (procs.length === 0) { procs = null; }
|
|
return procs;
|
|
};
|
|
|
|
//
|
|
// ### function findByUid (uid, processes)
|
|
// #### @uid {string} The uid of the process to find.
|
|
// #### @processes {Array} Set of processes to find in.
|
|
// Finds the process with the specified uid.
|
|
//
|
|
forever.findByUid = function (script, processes) {
|
|
var procs = !processes
|
|
? null
|
|
: processes.filter(function (p) {
|
|
return p.uid === script;
|
|
});
|
|
|
|
if (procs && procs.length === 0) { procs = null; }
|
|
return procs;
|
|
};
|
|
|
|
//
|
|
// ### function findByPid (pid, processes)
|
|
// #### @pid {string} The pid of the process to find.
|
|
// #### @processes {Array} Set of processes to find in.
|
|
// Finds the process with the specified pid.
|
|
//
|
|
forever.findByPid = function (pid, processes) {
|
|
pid = typeof pid === 'string'
|
|
? parseInt(pid, 10)
|
|
: pid;
|
|
|
|
var procs = processes && processes.filter(function (p) {
|
|
return p.pid === pid;
|
|
});
|
|
|
|
if (procs && procs.length === 0) { procs = null; }
|
|
return procs || null;
|
|
};
|
|
|
|
//
|
|
// ### function format (format, procs)
|
|
// #### @format {Boolean} Value indicating if processes should be formatted
|
|
// #### @procs {Array} Processes to format
|
|
// Returns a formatted version of the `procs` supplied based on the column
|
|
// configuration in `forever.config`.
|
|
//
|
|
forever.format = function (format, procs) {
|
|
if (!procs || procs.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
var index = 0,
|
|
columns = forever.config.get('columns'),
|
|
rows = [[' '].concat(columns)],
|
|
formatted;
|
|
|
|
function mapColumns(prefix, mapFn) {
|
|
return [prefix].concat(columns.map(mapFn));
|
|
}
|
|
|
|
if (format) {
|
|
//
|
|
// Iterate over the procs to see which has the
|
|
// longest options string
|
|
//
|
|
procs.forEach(function (proc) {
|
|
rows.push(mapColumns('[' + index + ']', function (column) {
|
|
return forever.columns[column]
|
|
? forever.columns[column].get(proc)
|
|
: 'MISSING';
|
|
}));
|
|
|
|
index++;
|
|
});
|
|
|
|
formatted = cliff.stringifyRows(rows, mapColumns('white', function (column) {
|
|
return forever.columns[column]
|
|
? forever.columns[column].color
|
|
: 'white';
|
|
}));
|
|
}
|
|
|
|
return format ? formatted : procs;
|
|
};
|
|
|
|
//
|
|
// ### function cleanUp ()
|
|
// Utility function for removing excess pid and
|
|
// config, and log files used by forever.
|
|
//
|
|
forever.cleanUp = function (cleanLogs, allowManager) {
|
|
var emitter = new events.EventEmitter(),
|
|
pidPath = forever.config.get('pidPath');
|
|
|
|
getAllProcesses(function (err, processes) {
|
|
if (err) {
|
|
return process.nextTick(function () {
|
|
emitter.emit('error', err);
|
|
});
|
|
}
|
|
else if (cleanLogs) {
|
|
forever.cleanLogsSync(processes);
|
|
}
|
|
|
|
function unlinkProcess(proc, done) {
|
|
fs.unlink(path.join(pidPath, proc.uid + '.pid'), function () {
|
|
//
|
|
// Ignore errors (in case the file doesnt exist).
|
|
//
|
|
|
|
if (cleanLogs && proc.logFile) {
|
|
//
|
|
// If we are cleaning logs then do so if the process
|
|
// has a logfile.
|
|
//
|
|
return fs.unlink(proc.logFile, function () {
|
|
done();
|
|
});
|
|
}
|
|
|
|
done();
|
|
});
|
|
}
|
|
|
|
function cleanProcess(proc, done) {
|
|
if (proc.child && proc.manager) {
|
|
return done();
|
|
}
|
|
else if (!proc.child && !proc.manager
|
|
|| (!proc.child && proc.manager && allowManager)
|
|
|| proc.dead) {
|
|
return unlinkProcess(proc, done);
|
|
}
|
|
|
|
//
|
|
// If we have a manager but no child, wait a moment
|
|
// in-case the child is currently restarting, but **only**
|
|
// if we have not already waited for this process
|
|
//
|
|
if (!proc.waited) {
|
|
proc.waited = true;
|
|
return setTimeout(function () {
|
|
checkProcess(proc, done);
|
|
}, 500);
|
|
}
|
|
|
|
done();
|
|
}
|
|
|
|
function checkProcess(proc, next) {
|
|
proc.child = forever.checkProcess(proc.pid);
|
|
proc.manager = forever.checkProcess(proc.foreverPid);
|
|
cleanProcess(proc, next);
|
|
}
|
|
|
|
if (processes && processes.length > 0) {
|
|
(function cleanBatch(batch) {
|
|
async.forEach(batch, checkProcess, function () {
|
|
return processes.length > 0
|
|
? cleanBatch(processes.splice(0, 10))
|
|
: emitter.emit('cleanUp');
|
|
});
|
|
})(processes.splice(0, 10));
|
|
}
|
|
else {
|
|
process.nextTick(function () {
|
|
emitter.emit('cleanUp');
|
|
});
|
|
}
|
|
});
|
|
|
|
return emitter;
|
|
};
|
|
|
|
//
|
|
// ### function cleanLogsSync (processes)
|
|
// #### @processes {Array} The set of all forever processes
|
|
// Removes all log files from the root forever directory
|
|
// that do not belong to current running forever processes.
|
|
//
|
|
forever.cleanLogsSync = function (processes) {
|
|
var root = forever.config.get('root'),
|
|
files = fs.readdirSync(root),
|
|
running,
|
|
runningLogs;
|
|
|
|
running = processes && processes.filter(function (p) {
|
|
return p && p.logFile;
|
|
});
|
|
|
|
runningLogs = running && running.map(function (p) {
|
|
return p.logFile.split('/').pop();
|
|
});
|
|
|
|
files.forEach(function (file) {
|
|
if (/\.log$/.test(file) && (!runningLogs || runningLogs.indexOf(file) === -1)) {
|
|
fs.unlinkSync(path.join(root, file));
|
|
}
|
|
});
|
|
};
|
|
|
|
//
|
|
// ### function logFilePath (logFile)
|
|
// #### @logFile {string} Log file path
|
|
// Determines the full logfile path name
|
|
//
|
|
forever.logFilePath = function (logFile, uid) {
|
|
return logFile && (logFile[0] === '/' || logFile[1] === ':')
|
|
? logFile
|
|
: path.join(forever.config.get('root'), logFile || (uid || 'forever') + '.log');
|
|
};
|
|
|
|
//
|
|
// ### function pidFilePath (pidFile)
|
|
// #### @logFile {string} Pid file path
|
|
// Determines the full pid file path name
|
|
//
|
|
forever.pidFilePath = function (pidFile) {
|
|
return pidFile && (pidFile[0] === '/' || pidFile[1] === ':')
|
|
? pidFile
|
|
: path.join(forever.config.get('pidPath'), pidFile);
|
|
};
|
|
|
|
//
|
|
// ### @function logEvents (monitor)
|
|
// #### @monitor {forever.Monitor} Monitor to log events for
|
|
// Logs important restart and error events to `console.error`
|
|
//
|
|
forever.logEvents = function (monitor) {
|
|
monitor.on('watch:error', function (info) {
|
|
forever.out.error(info.message);
|
|
forever.out.error(info.error);
|
|
});
|
|
|
|
monitor.on('watch:restart', function (info) {
|
|
forever.out.error('restarting script because ' + info.file + ' changed');
|
|
});
|
|
|
|
monitor.on('restart', function () {
|
|
forever.out.error('Script restart attempt #' + monitor.times);
|
|
});
|
|
|
|
monitor.on('exit:code', function (code, signal) {
|
|
forever.out.error((code !== null && code !== undefined)
|
|
? 'Forever detected script exited with code: ' + code
|
|
: 'Forever detected script was killed by signal: ' + signal);
|
|
});
|
|
};
|
|
|
|
//
|
|
// ### @columns {Object}
|
|
// Property descriptors for accessing forever column information
|
|
// through `forever list` and `forever.list()`
|
|
//
|
|
forever.columns = {
|
|
uid: {
|
|
color: 'white',
|
|
get: function (proc) {
|
|
return proc.uid;
|
|
}
|
|
},
|
|
id: {
|
|
color: 'white',
|
|
get: function (proc) {
|
|
return proc.id ? proc.id : '';
|
|
}
|
|
},
|
|
command: {
|
|
color: 'grey',
|
|
get: function (proc) {
|
|
return (proc.command || 'node').grey;
|
|
}
|
|
},
|
|
script: {
|
|
color: 'grey',
|
|
get: function (proc) {
|
|
return [proc.file].concat(proc.args).join(' ').grey;
|
|
}
|
|
},
|
|
forever: {
|
|
color: 'white',
|
|
get: function (proc) {
|
|
return proc.foreverPid;
|
|
}
|
|
},
|
|
pid: {
|
|
color: 'white',
|
|
get: function (proc) {
|
|
return proc.pid;
|
|
}
|
|
},
|
|
logfile: {
|
|
color: 'magenta',
|
|
get: function (proc) {
|
|
return proc.logFile ? proc.logFile.magenta : '';
|
|
}
|
|
},
|
|
dir: {
|
|
color: 'grey',
|
|
get: function (proc) {
|
|
return proc.sourceDir.grey;
|
|
}
|
|
},
|
|
uptime: {
|
|
color: 'yellow',
|
|
get: function (proc) {
|
|
return proc.running ? timespan.fromDates(new Date(proc.ctime), new Date()).toString().yellow : "STOPPED".red;
|
|
}
|
|
}
|
|
};
|