diff --git a/docs/forever.html b/docs/forever.html index d54e1f87..7abd47a4 100644 --- a/docs/forever.html +++ b/docs/forever.html @@ -6,64 +6,78 @@ * */ -require.paths.unshift(__dirname); - var fs = require('fs'), - colors = require('colors'), - async = require('async'), path = require('path'), events = require('events'), exec = require('child_process').exec, - timespan = require('timespan'), + async = require('async'), + colors = require('colors'), + cliff = require('cliff'), + daemon = require('daemon'), nconf = require('nconf'), - daemon = require('daemon'); + timespan = require('timespan'), + winston = require('winston'); -var forever = exports;

Export Components / Settings

+var forever = exports;

Setup forever.log to be a custom winston logger.

forever.log = new (winston.Logger)({ 
+  transports: [
+    new (winston.transports.Console)()
+  ]
+});
 
-

Export version and important Prototypes from lib/forever/*

forever.version     = [0, 4, 0];
-forever.initialized = false;
+forever.log.cli();

Export Components / Settings

+ +

Export version and important Prototypes from lib/forever/*

forever.initialized = false;
 forever.root        = path.join(process.env.HOME, '.forever');
 forever.config      = new nconf.stores.File({ file: path.join(forever.root, 'config.json') });
-forever.cli         = require('forever/cli');
-forever.Forever     = forever.Monitor = require('forever/monitor').Monitor; 

function load (options, [callback])

+forever.Forever = forever.Monitor = require('./forever/monitor').Monitor; +forever.cli = require('./forever/cli');

Expose version through pkginfo

require('pkginfo')(module, 'version');

function load (options, [callback])

@options {Object} Options to load into the forever module

-

Initializes configuration for forever module

forever.load = function (options) {

Setup the incoming options with default options.

  options         = options || {};
-  options.root    = options.root || forever.root,
+

Initializes configuration for forever module

forever.load = function (options) {

Setup the incoming options with default options.

  options         = options || {};
+  options.root    = options.root || forever.root;
   options.pidPath = options.pidPath || path.join(options.root, 'pids');
-  

If forever is initalized and the config directories are identical +

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.stores.File({ file: path.join(options.root, 'config.json') });
-  

Try to load the forever config.json from -the specified location.

  try {
-    forever.config.loadSync();
-  }
+  

Try to load the forever config.json from +the specified location.

  try { forever.config.loadSync() }
   catch (ex) { }
   
   forever.config.set('root', options.root);
   forever.config.set('pidPath', options.pidPath);
-  

Syncronously create the root directory +

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); }
+    try { fs.mkdirSync(dir, 0755) }
     catch (ex) { }
   }
 
   tryCreate(forever.config.get('root'));
   tryCreate(forever.config.get('pidPath'));
-  

Attempt to save the new config.json for forever

  try {
-    forever.config.saveSync();
-  }
+  

Attempt to save the new config.json for forever

  try { forever.config.saveSync() }
   catch (ex) { }
   
   forever.initialized = true;
-};

Ensure forever will always be loaded the first time it is required.

forever.load();

function stat (logFile, script, callback)

+};

@private function _debug ()

+ +

Sets up debugging for this forever process

forever._debug = function () {
+  forever.config.set('debug', true);
+  forever.log.add(winston.transports.File, { 
+    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

@@ -75,28 +89,25 @@

@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,
-      realCallback = callback;
+  var logAppend;
       
   if (arguments.length === 4) {
     logAppend = callback;
-    realCallback = arguments[3];
+    callback = arguments[3];
   }
 
   fs.stat(script, function (err, stats) {
-    if (err) return realCallback(new Error('script ' + script + ' does not exist.'));
-
-    if (logAppend) {
-      realCallback(null);
-      return;
+    if (err) {
+      return callback(new Error('script ' + script + ' does not exist.'));
     }
 
-    fs.stat(logFile, function (err, stats) {
-      if (!err) return realCallback(new Error('log file ' + logFile + ' exists.'));
-      realCallback(null);
+    return logAppend ? callback(null) : fs.stat(logFile, function (err, stats) {
+      return !err 
+        ? callback(new Error('log file ' + logFile + ' exists.')) 
+        : callback(null);
     });
   });
-};

function start (script, options)

+};

function start (script, options)

@script {string} Location of the script to run.

@@ -104,7 +115,7 @@

@options {Object} Configuration for forever instance.

Starts a script with forever

forever.start = function (script, options) {
   return new forever.Monitor(script, options).start();
-};

function startDaemon (script, options)

+};

function startDaemon (script, options)

@script {string} Location of the script to run.

@@ -113,13 +124,16 @@

@options {Object} Configuration for forever instance.

Starts a script with forever as a daemon

forever.startDaemon = function (script, options) {
   options.logFile = forever.logFilePath(options.logFile);
   options.pidFile = forever.pidFilePath(options.pidFile);
+  
   var runner = new forever.Monitor(script, options);
   
   fs.open(options.logFile, options.appendLog ? 'a+' : 'w+', function (err, fd) {
-    if (err) return runner.emit('error', err);
+    if (err) {
+      return runner.emit('error', err);
+    }
 
     var pid = daemon.start(fd);
-    daemon.lock(options.pidFile);

Remark: This should work, but the fd gets screwed up + daemon.lock(options.pidFile);

Remark: This should work, but the fd gets screwed up with the daemon process.

process.on('exit', function () { @@ -129,7 +143,7 @@

@options {Object} Configuration for forever instance.

}); return runner; -};

function stop (target, [format])

+};

function stop (target, [format])

@target {string} Index or script name to stop

@@ -141,16 +155,16 @@

@format {boolean} Indicated if we should CLI format the returned output.

processes = getAllProcesses(), results = []; - var procs = /(\d+)/.test(target) ? forever.findByIndex(target, processes) - : forever.findByScript(target, processes); - + var procs = /(\d+)/.test(target) + ? forever.findByIndex(target, processes) + : forever.findByScript(target, processes); + if (procs && procs.length > 0) { procs.forEach(function (proc) { - try { - process.kill(proc.foreverPid); - process.kill(proc.pid); - } - catch (ex) { } + [proc.foreverPid, proc.pid].forEach(function (pid) { + try { process.kill(pid) } + catch (ex) { } + }); }); process.nextTick(function () { @@ -164,7 +178,7 @@

@format {boolean} Indicated if we should CLI format the returned output.

} return emitter; -};

function restart (target, format)

+};

function restart (target, format)

@target {string} Index or script name to restart

@@ -177,7 +191,7 @@

@format {boolean} Indicated if we should CLI format the returned output.

runner.on('stop', function (procs) { if (procs && procs.length > 0) { - async.forEach(procs, function (proc, next) {

We need to spawn a new process running the forever CLI + async.forEach(procs, function (proc, next) {

We need to spawn a new process running the forever CLI here because we want each process to daemonize separately without the main process running forever restart myscript.js daemonizing itself.

        var restartCommand = [
@@ -188,6 +202,10 @@ 

@format {boolean} Indicated if we should CLI format the returned output.

'--append' ]; + if (proc.silent) { + restartCommand.push('--silent'); + } + if (proc.outFile) { restartCommand.push('-o', path.join(proc.sourceDir, proc.outFile)); } @@ -208,28 +226,31 @@

@format {boolean} Indicated if we should CLI format the returned output.

emitter.emit('error', new Error('Cannot find forever process: ' + target)); } }); -

Bubble up the error to the appropriate EventEmitter instance.

  runner.on('error', function (err) {
+  

Bubble up the error to the appropriate EventEmitter instance.

  runner.on('error', function (err) {
     emitter.emit('error', err);
   });
   
   return emitter;
-};

function findByIndex (index, processes)

+};

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) {
-  return processes && [processes[parseInt(index)]];
-};

function findByScript (script, processes)

+ var proc = processes && processes[parseInt(index)]; + 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) {
-  return processes.filter(function (p) { return p.file === script });
-};

function stopAll (format)

+ return processes + ? processes.filter(function (p) { return p.file === script }) + : null; +};

function stopAll (format)

@format {boolean} Value indicating if we should format output

@@ -248,7 +269,9 @@

@format {boolean} Value indicating if we should format output

fPids.concat(cPids).forEach(function (pid) { try { - process.kill(pid); + if (pid !== process.pid) { + process.kill(pid); + } } catch (ex) { } }); @@ -264,62 +287,124 @@

@format {boolean} Value indicating if we should format output

} return emitter; -};

function list (format, procs)

+};

function list (format, procs)

@format {boolean} If set, will return a formatted string of data

@procs {Array} Set of processes to list format.

Returns the list of all process data managed by forever.

forever.list = function (format, procs) {
-  var formatted = [];
+  var formatted;
   
   procs = procs || getAllProcesses();
-  if (!procs) return null;
+  if (!procs) {
+    return null;
+  }
   
   if (format) {
-    var index = 0, maxLen = 0;

Iterate over the procs to see which has the longest options string

    procs.forEach(function (proc) {
-      proc.length = [proc.file].concat(proc.options).join(' ').length;
-      if (proc.length > maxLen) maxLen = proc.length;
-    });
-    
-    procs.forEach(function (proc) {

Create padding string to keep output aligned

      var padding = new Array(maxLen - proc.length + 1).join(' ');
-      formatted.push(formatProcess(proc, index, padding));
+    var index = 0, rows = [
+      ['   ', 'command ', 'script', 'forever ', 'pid', 'logfile', 'uptime']
+    ];
+    

Iterate over the procs to see which has the +longest options string

    procs.forEach(function (proc) {
+      rows.push([
+        '[' + index + ']',
+        (proc.command || 'node').grey,
+        [proc.file].concat(proc.options).join(' ').grey,
+        proc.foreverPid,
+        proc.pid, 
+        proc.logFile ? proc.logFile.magenta : '',
+        timespan.fromDates(new Date(proc.ctime), new Date()).toString().yellow
+      ]);
+      
       index++;
     });
+    
+    formatted = cliff.stringifyRows(rows, [
+      'white', 
+      'grey',
+      'grey',
+      'white',
+      'white',
+      'magenta',
+      'yellow'
+    ]);
   }
   
-  return format ? formatted.join('\n') : procs;
-};

function cleanUp ()

+ return format ? formatted : procs; +};

function cleanUp ()

Utility function for removing excess pid and -config, and log files used by forever.

forever.cleanUp = function (cleanLogs) {
+config, and log files used by forever.

forever.cleanUp = function (cleanLogs, allowManager) {
   var emitter = new events.EventEmitter(),
-      processes = getAllProcesses(true);
+      processes = getAllProcesses(true),
+      pidPath = forever.config.get('pidPath');
   
-  if (cleanLogs) forever.cleanLogsSync(processes);
+  if (cleanLogs) {
+    forever.cleanLogsSync(processes);
+  }
  
   if (processes && processes.length > 0) {
-    var checked = 0;
-    processes.forEach(function (proc) {
-      checkProcess(proc.pid, function (child) {
-        checkProcess(proc.foreverPid, function (manager) {
-          if (!child && !manager || proc.dead) {
-            fs.unlink(path.join(forever.config.get('pidPath'), proc.uid + '.fvr'), function () {
-              fs.unlink(path.join(forever.config.get('pidPath'), proc.uid + '.pid'), function () {

Ignore errors

              });
-            });
-            
-            if (cleanLogs && proc.logFile) {
-              fs.unlink(proc.logFile, function () { /* Ignore Errors */ });
-            }
-          }
-          
-          checked++;
-          if (checked === processes.length) {
-            emitter.emit('cleanUp');
-          }
+    function tryUnlink (file, next) {
+      fs.unlink(file, function () { 

Ignore errors (in case the file doesnt exist).

        next();
+      });
+    }
+    
+    function unlinkProcess (proc, done) {
+      var files = [
+        path.join(pidPath, proc.uid + '.fvr'),
+        path.join(pidPath, proc.uid + '.pid')
+      ];
+      
+      async.forEach(files, tryUnlink, function () {
+        if (cleanLogs && proc.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) {
+      forever.checkProcess(proc.pid, function (child) {
+        proc.child = child;
+        forever.checkProcess(proc.foreverPid, function (manager) {
+          proc.manager = manager;
+          cleanProcess(proc, next);
         });
       });
-    });
+    }
+    
+    (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 () {
@@ -328,95 +413,84 @@ 

@procs {Array} Set of processes to list format.

} return emitter; -};

function cleanLogsSync (processes)

+};

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 files = fs.readdirSync(forever.config.get('root')),
+  var root = forever.config.get('root'),
+      files = fs.readdirSync(root),
       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(forever.config.get('root'), file));
+      fs.unlinkSync(path.join(root, file));
     }
   });
-};

function randomString (bits)

+};

function randomString (bits)

@bits {Number} Bit-length of the base64 string to return.

Returns a pseude-random ASCII string which contains at least the specified number of bits of entropy the return value is a string of length ⌈bits/6⌉ of characters from the base64 alphabet.

forever.randomString = function (bits) {
-  var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-+', 
+  var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$', 
       rand, i, ret = '';
-  

in v8, Math.random() yields 32 pseudo-random bits (in spidermonkey it gives 53)

  while (bits > 0) {
-    rand = Math.floor(Math.random()*0x100000000) // 32-bit integer

base 64 means 6 bits per character, so we use the top 30 bits from rand to give 30/6=5 characters.

    for (i=26; i>0 && bits>0; i-=6, bits-=6) { 
+  

in v8, Math.random() yields 32 pseudo-random bits (in spidermonkey it gives 53)

  while (bits > 0) {
+    rand = Math.floor(Math.random()*0x100000000) // 32-bit integer

base 64 means 6 bits per character, so we use the top 30 bits from rand to give 30/6=5 characters.

    for (i=26; i>0 && bits>0; i-=6, bits-=6) { 
       ret+=chars[0x3F & rand >>> i];
     }
   }
+  
   return ret;
-};

function logFilePath (logFile)

+};

function logFilePath (logFile)

@logFile {string} Log file path

Determines the full logfile path name

forever.logFilePath = function(logFile, uid) {
-  if (logFile && logFile[0] === '/') {
-    return logFile;
-  } else {
-    return path.join(forever.config.get('root'), logFile || (uid || 'forever') + '.log');
-  }
-};

function pidFilePath (pidFile)

+ return logFile && logFile[0] === '/' + ? 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) {
-  if (pidFile && pidFile[0] === '/') {
-    return pidFile;
-  } else {
-    return path.join(forever.config.get('pidPath'), pidFile);
-  }
-};

function checkProcess (pid, callback)

+ return pidFile && pidFile[0] === '/' + ? pidFile + : path.join(forever.config.get('pidPath'), pidFile); +};

function checkProcess (pid, callback)

@pid {string} pid of the process to check

@callback {function} Continuation to pass control backto.

-

Utility function to check to see if a pid is running

function checkProcess (pid, callback) {
+

Utility function to check to see if a pid is running

forever.checkProcess = function (pid, callback) {
   if (!pid) {
     return callback(false);
   }
   
   exec('ps ' + pid + ' | grep -v PID', function (err, stdout, stderr) {
-    if (err) return callback(false);
+    if (err) {
+      return callback(false);
+    }
+    
     callback(stdout.indexOf(pid) !== -1);
   });
-};

function formatProcess (proc index, padding)

- -

@proc {Object} Process to format

- -

@index {Number} Index of the process in the set of all processes

- -

@padding {string} Padding to add to the formatted output

- -

Returns a formatted string for the process @proc at -the specified index.

function formatProcess (proc, index, padding) {

Create an array of the output we can later join

  return ['  [' + index + ']', proc.file.grey]
-    .concat(proc.options.map(function (opt) { return opt.grey }))
-    .concat([padding + '[' + proc.pid + ',', proc.foreverPid + ']'])
-    .concat(proc.logFile ? proc.logFile.magenta : '')
-    .concat(timespan.fromDates(new Date(proc.ctime), new Date()).toString().yellow)
-    .join(' ');
-};

function getAllProcess ([findDead])

+};

function getAllProcess ([findDead])

@findDead {boolean} Optional parameter that indicates to return dead procs

Returns all data for processes managed by forever.

function getAllProcesses (findDead) {
-  var results = [], processes = {},
+  var results = [], 
+      processes = {},
       files = fs.readdirSync(forever.config.get('pidPath'));
   
-  if (files.length === 0) return null;
+  if (files.length === 0) {
+    return null;
+  }
   
   files.forEach(function (file) {
     try {
@@ -428,7 +502,7 @@ 

@findDead {boolean} Optional parameter that indicates to return dead procsswitch (ext) { case '.pid': var pid = parseInt(data); - if (!processes[uid]) processes[uid] = { + processes[uid] = processes[uid] || { foreverPid: pid, uid: uid }; @@ -440,20 +514,25 @@

@findDead {boolean} Optional parameter that indicates to return dead procsbreak; } } - catch (ex) {

Ignore errors

      processes[uid] = {
+    catch (ex) {

Ignore errors

      processes[uid] = {
         uid: uid
       };
     }
   });
   
   Object.keys(processes).forEach(function (key) {
-    if (!processes[key].pid && !findDead) return;
-    else if (!processes[key].pid) processes[key].dead = true;
+    if (!processes[key].pid && !findDead) {
+      return;
+    }
+    else if (!processes[key].pid) {
+      processes[key].dead = true;
+    }
+    
     results.push(processes[key]);
   });
   
   return results;
-};

function getAllPids ()

+};

function getAllPids ()

Returns the set of all pids managed by forever. e.x. [{ pid: 12345, foreverPid: 12346 }, ...]

function getAllPids (processes) {
diff --git a/docs/forever/cli.html b/docs/forever/cli.html
index d58cc31e..dbba4835 100644
--- a/docs/forever/cli.html
+++ b/docs/forever/cli.html
@@ -8,27 +8,12 @@
 
 var sys = require('sys'),
     path = require('path'),
-    eyes = require('eyes'),
-    winston = require('winston'),
-    forever = require('forever');
+    cliff = require('cliff'),
+    forever = require('../forever');
 
 var cli = exports;
 
-var reserved = ['root', 'pidPath'];
-
-var inspect = eyes.inspector({ stream: null,
-  styles: {               // Styles applied to stdout
-    all:     null,        // Overall style applied to everything
-    label:   'underline', // Inspection labels, like 'array' in `array: [1, 2, 3]`
-    other:   'inverted',  // Objects which don't have a literal representation, such as functions
-    key:     'grey',      // The keys in object literals, like 'a' in `{a: 1}`
-    special: 'grey',      // null, undefined...
-    number:  'blue',      // 1, 2, 3, etc
-    bool:    'magenta',   // true false
-    regexp:  'green',     // /\d+/
-    string:  'yellow'
-  }
-});

function exec (action, file, options)

+var reserved = ['root', 'pidPath'];

function exec (action, file, options)

@action {string} CLI action to execute

@@ -37,19 +22,25 @@

@file {string} Location of the target forever script or process.

@options {Object} Options to pass to forever for the action.

Executes the action in forever with the specified file and options.

cli.exec = function (action, file, options) {
-  winston.info('Running action: ' + action.yellow);
+  if (action) {
+    forever.log.info('Running action: ' + action.yellow);
+  }
   
-  winston.silly('Tidying ' + forever.config.get('root'));
+  forever.log.silly('Tidying ' + forever.config.get('root'));
   var tidy = forever.cleanUp(action === 'cleanlogs'); 
   tidy.on('cleanUp', function () {
-    winston.silly(forever.config.get('root') + ' tidied.');
+    forever.log.silly(forever.config.get('root') + ' tidied.');
 
-    if (file && action !== 'set' && action !== 'clear') {
-      winston.info('Forever processing file: ' + file.grey);
+    if (file && action === 'start') {
+      forever.log.info('Forever processing file: ' + file.grey);
+    }
+    
+    if (options.command) {
+      forever.log.info('Forever using command: ' + options.command.grey);
     }
 
     if (options && action !== 'set') {
-      winston.silly('Forever using options', options);
+      forever.log.silly('Forever using options', options);
     }

If there is no action then start in the current process with the specified file and options.

    if (!action) {
       return cli.start(file, options);
@@ -58,8 +49,7 @@ 

@options {Object} Options to pass to forever for the action.return; } - var daemon = true; - cli[action](file, options, daemon); + cli[action](file, options, true); }); };

function start (file, options, daemon)

@@ -85,12 +75,12 @@

@file {string} Target forever process to stop

var runner = forever.stop(file, true); runner.on('stop', function (process) { - winston.info('Forever stopped process:'); - sys.puts(process); + forever.log.info('Forever stopped process:'); + forever.log.data(process); }); runner.on('error', function (err) { - winston.error('Forever cannot find process with index: ' + file) + forever.log.error('Forever cannot find process with index: ' + file) }); };

function stopall ()

@@ -98,11 +88,13 @@

@file {string} Target forever process to stop

var runner = forever.stopAll(true); runner.on('stopAll', function (processes) { if (processes) { - winston.info('Forever stopped processes:'); - sys.puts(processes); + forever.log.info('Forever stopped processes:'); + processes.split('\n').forEach(function (line) { + forever.log.data(line); + }); } else { - winston.info('No forever processes running'); + forever.log.info('No forever processes running'); } }); };

function restart (file)

@@ -113,29 +105,38 @@

@file {string} Target process to restart

var runner = forever.restart(file, true); runner.on('restart', function (processes) { if (processes) { - winston.info('Forever restarted processes:'); - sys.puts(processes); + forever.log.info('Forever restarted process(es):'); + processes.split('\n').forEach(function (line) { + forever.log.data(line); + }); } else { - winston.info('No forever processes running'); + forever.log.info('No forever processes running'); } }); + + runner.on('error', function (err) { + forever.log.error('Error restarting process: ' + file.grey); + forever.log.error(err.message); + }); };

function list ()

Lists all currently running forever processes.

cli.list = function () {
   var processes = forever.list(true);
   if (processes) {
-    winston.info('Forever processes running');
-    sys.puts(processes);
+    forever.log.info('Forever processes running');
+    processes.split('\n').forEach(function (line) {
+      forever.log.data(line);
+    })
   }
   else {
-    winston.info('No forever processes running');
+    forever.log.info('No forever processes running');
   }
 };

function config ()

Lists all of the configuration in ~/.forever/config.json.

cli.config = function () {
   var keys = Object.keys(forever.config.store),
-      conf = inspect(forever.config.store);
+      conf = cliff.inspect(forever.config.store);
   
   if (keys.length <= 2) {
     conf = conf.replace(/\{\s/, '{ \n')
@@ -148,7 +149,7 @@ 

@file {string} Target process to restart

} conf.split('\n').forEach(function (line) { - winston.info(line); + forever.log.data(line); }); };

function set (key, value)

@@ -159,11 +160,11 @@

@value {string} Value to set for key

Sets the specified key / value pair in the forever user config.

cli.set = function (key, value) {
   if (!key || !value) {
-    return winston.error('Both <key> and <value> are required.');
+    return forever.log.error('Both <key> and <value> are required.');
   }
   
   updateConfig(function () {
-    winston.info('Setting forever config: ' + key.grey);
+    forever.log.info('Setting forever config: ' + key.grey);
     forever.config.set(key, value);
   });
 };

function clear (key)

@@ -172,16 +173,16 @@

@key {string} Key to remove from ~/.forever/config.json

Removes the specified key from the forever user config.

cli.clear = function (key) {
   if (reserved.indexOf(key) !== -1) {
-    winston.warn('Cannot clear reserved config: ' + key.grey);
-    winston.warn('Use `forever set ' + key + '` instead');
+    forever.log.warn('Cannot clear reserved config: ' + key.grey);
+    forever.log.warn('Use `forever set ' + key + '` instead');
     return;
   }
   
   updateConfig(function () {
-    winston.info('Clearing forever config: ' + key.grey);
+    forever.log.info('Clearing forever config: ' + key.grey);
     forever.config.clear(key);
   });
-};

function (file, options, callback)

+};

@private function (file, options, callback)

@file {string} Target script to start

@@ -191,20 +192,21 @@

@callback {function} Continuation to respond to when complete.

Helper function that sets up the pathing for the specified file then stats the appropriate files and responds.

function tryStart (file, options, callback) {
-  var fullLog, fullScript
+  var fullLog, fullScript;
 
   fullLog = forever.logFilePath(options.logFile, options.uid);
   fullScript = path.join(options.sourceDir, file);
   
   forever.stat(fullLog, fullScript, options.appendLog, function (err) {
     if (err) {
-      winston.error('Cannot start forever: ' + err.message);
+      forever.log.error('Cannot start forever');
+      forever.log.error(err.message);
       process.exit(-1);
     }
   
     callback();
   });
-}

function updateConfig (updater)

+}

@private function updateConfig (updater)

@updater {function} Function which updates the forever config

@@ -213,11 +215,12 @@

@updater {function} Function which updates the forever config

updater(); forever.config.save(function (err) { if (err) { - return winston.error('Error saving config: ' + err.message); + return forever.log.error('Error saving config: ' + err.message); } cli.config(); - winston.info('Forever config saved: ' + path.join(forever.config.get('root'), 'config.json').yellow); + var configFile = path.join(forever.config.get('root'), 'config.json'); + forever.log.info('Forever config saved: ' + configFile.yellow); }); } diff --git a/docs/forever/monitor.html b/docs/forever/monitor.html index ab93a3c3..6fafc164 100644 --- a/docs/forever/monitor.html +++ b/docs/forever/monitor.html @@ -12,7 +12,7 @@ events = require('events'), spawn = require('child_process').spawn, winston = require('winston'), - forever = require('forever');

function Monitor (script, options)

+ forever = require('../forever');

function Monitor (script, options)

@script {string} Location of the target script to run.

@@ -20,27 +20,32 @@

@options {Object} Configuration for this instance.

Creates a new instance of forever with specified params.

var Monitor = exports.Monitor = function (script, options) {
   events.EventEmitter.call(this);
-  
-  options         = options || {};
-  this.silent     = options.silent || false;
-  this.forever    = options.forever || false;
-  this.command    = options.command || 'node';
-  this.sourceDir  = options.sourceDir;
-  this.minUptime  = typeof options.minUptime !== 'number' ? 2000 : options.minUptime;
-  this.options    = options.options || [];
-  this.spawnWith  = options.spawnWith || null;
-  this.uid        = options.uid || forever.randomString(24);
-  this.max        = options.max;
-  this.logFile    = options.logFile || path.join(forever.config.get('root'), this.uid + '.log');
-  this.pidFile    = options.pidFile || path.join(forever.config.get('pidPath'), this.uid + '.pid');
-  this.outFile    = options.outFile;
-  this.errFile    = options.errFile;
-  this.logger     = options.logger || new (winston.Logger)({
+  

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.fvrFile     = path.join(forever.config.get('pidPath'), this.uid + '.fvr');
+  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' ? 2000 : 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);
-  
-  this.childExists = false;
+  

Extend from the winston logger.

  this.logger.extend(this);
   
   if (Array.isArray(script)) {
     this.command = script[0];
@@ -53,15 +58,13 @@ 

@options {Object} Configuration for this instance.

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) {
+  

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) {
+  

If we should log stderr, open a file buffer

  if (this.errFile) {
     this.stderr = fs.createWriteStream(this.errFile, { flags: 'a+', encoding: 'utf8', mode: 0666 });
   }
-  
-  this.times = 0;
-};

Inherit from events.EventEmitter

sys.inherits(Monitor, events.EventEmitter);

function start ([restart])

+};

Inherit from events.EventEmitter

sys.inherits(Monitor, events.EventEmitter);

function start ([restart])

@restart {boolean} Value indicating whether this is a restart.

@@ -91,12 +94,12 @@

@restart {boolean} Value indicating whether this is a restart.

}); this.save(); -

Hook all stream data and process it

  function listenTo (stream) {
+  

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 + 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);
+      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);
@@ -108,30 +111,49 @@ 

@restart {boolean} Value indicating whether this is a restart.

child[stream].removeListener('data', ldata); }); } -

Listen to stdout and stderr

  listenTo('stdout');
+  

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);
-    
-    if ((self.forever || self.times < self.max) && !self.forceStop && !spinning) {
-      self.times++;
+
+    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();
+      }
+      
+      fs.unlink(self.fvrFile, function () {
+        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 {
-      this.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);
+      restartChild();
     }
   });
   
   return this;
-};

function trySpawn()

+};

function trySpawn()

Tries to spawn the target Forever child process. Depending on configuration, it checks the first argument of the options @@ -147,11 +169,15 @@

@restart {boolean} Value indicating whether this is a restart.

} } + this.spawnWith.cwd = this.cwd || this.spawnWith.cwd; + this.spawnWith.env = this._getEnv(); + return spawn(this.command, this.options, this.spawnWith); -};

function save ()

+};

function save ()

Persists this instance of forever to disk.

Monitor.prototype.save = function () {
   var self = this;
+  
   if (!this.running) {
     process.nextTick(function () {
       self.emit('error', new Error('Cannot save Forever instance that is not running'));
@@ -159,30 +185,34 @@ 

@restart {boolean} Value indicating whether this is a restart.

} var childData = { - uid: this.uid, ctime: this.ctime, - pid: this.child.pid, + command: this.command, + file: this.options[0], foreverPid: process.pid, logFile: this.logFile, options: this.options.slice(1), - file: this.options[0] + pid: this.child.pid, + silent: this.silent, + uid: this.uid }; - this.childData = childData; - if (this.pidFile) childData.pidFile = this.pidFile; - if (this.outFile) childData.outFile = this.outFile; - if (this.errFile) childData.errFile = this.errFile; + ['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 + '/', ''); } - var childPath = path.join(forever.config.get('pidPath'), this.uid + '.fvr'); - fs.writeFile(childPath, JSON.stringify(childData, null, 2), function (err) { - if (err) self.emit('error', err); - self.emit('save', childPath, childData); + this.childData = childData; + + fs.writeFile(this.fvrFile, JSON.stringify(childData, null, 2), function (err) { + return err ? self.emit('error', err) : self.emit('save', self.fvrFile, childData); }); -

Setup the forever process to listen to +

Setup the forever process to listen to SIGINT and SIGTERM events so that we can clean up the *.pid file

@@ -200,15 +230,15 @@

@restart {boolean} Value indicating whether this is a restart.

fs.unlinkSync(childPath); });

  
   return this;
-};

function restart ()

+};

function restart ()

Restarts the target script associated with this instance.

Monitor.prototype.restart = function () {
   return this.kill(false);
-};

function stop ()

+};

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)

+};

function kill (forceStop)

@forceStop {boolean} Value indicating whether short circuit forever auto-restart.

@@ -220,7 +250,7 @@

@forceStop {boolean} Value indicating whether short circuit forever auto-res self.emit('error', new Error('Cannot stop process that is not running.')); }); } - else {

Set an instance variable here to indicate this + else {

Set an instance variable here to indicate this stoppage is forced so that when child.on('exit', ..) fires in Monitor.prototype.start we can short circuit and prevent auto-restart

    if (forceStop) {
@@ -232,6 +262,26 @@ 

@forceStop {boolean} Value indicating whether short circuit forever auto-res } 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 process.env and the custom +environment variables in this.env.

  Object.keys(process.env).forEach(function (k) { addKey(k, process.env) });
+  extra.forEach(function (k) { addKey(k, self.env) });
+  
+  return merged;
 };
 
 
\ No newline at end of file