1
0
mirror of https://github.com/mgerb/mywebsite synced 2026-01-12 02:42:48 +00:00
Files
mywebsite/node_modules/forever/lib/forever.js
2015-06-25 16:28:41 -05:00

1042 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) {
var procs = !processes
? null
: processes.filter(function (p) {
return p.pid == pid;
});
if (procs && procs.length === 0) { procs = null; }
return procs;
};
//
// ### 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(!isNaN(code)
? '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;
}
}
};