UNPKG

28.8 kBJavaScriptView Raw
1/*
2 * forever.js: Top level include for the forever module
3 *
4 * (C) 2010 Charlie Robbins & the Contributors
5 * MIT LICENCE
6 *
7 */
8
9var fs = require('fs'),
10 path = require('path'),
11 events = require('events'),
12 exec = require('child_process').exec,
13 spawn = require('child_process').spawn,
14 cliff = require('cliff'),
15 nconf = require('nconf'),
16 nssocket = require('nssocket'),
17 timespan = require('timespan'),
18 utile = require('utile'),
19 winston = require('winston'),
20 mkdirp = utile.mkdirp,
21 async = utile.async;
22
23var forever = exports;
24
25//
26// Setup `forever.log` to be a custom `winston` logger.
27//
28forever.log = new (winston.Logger)({
29 transports: [
30 new (winston.transports.Console)()
31 ]
32});
33
34forever.log.cli();
35
36//
37// Setup `forever out` for logEvents with `winston` custom logger.
38//
39forever.out = new (winston.Logger)({
40 transports: [
41 new (winston.transports.Console)()
42 ]
43});
44
45//
46// ### Export Components / Settings
47// Export `version` and important Prototypes from `lib/forever/*`
48//
49forever.initialized = false;
50forever.kill = require('forever-monitor').kill;
51forever.checkProcess = require('forever-monitor').checkProcess;
52forever.root = process.env.FOREVER_ROOT || path.join(process.env.HOME || process.env.USERPROFILE || '/root', '.forever');
53forever.config = new nconf.File({ file: path.join(forever.root, 'config.json') });
54forever.Forever = forever.Monitor = require('forever-monitor').Monitor;
55forever.Worker = require('./forever/worker').Worker;
56forever.cli = require('./forever/cli');
57
58//
59// Expose version through `pkginfo`
60//
61exports.version = require('../package').version;
62
63//
64// ### function getSockets (sockPath, callback)
65// #### @sockPath {string} Path in which to look for UNIX domain sockets
66// #### @callback {function} Continuation to pass control to when complete
67// Attempts to read the files from `sockPath` if the directory does not exist,
68// then it is created using `mkdirp`.
69//
70function getSockets(sockPath, callback) {
71 var sockets;
72
73 try {
74 sockets = fs.readdirSync(sockPath);
75 }
76 catch (ex) {
77 if (ex.code !== 'ENOENT') {
78 return callback(ex);
79 }
80
81 return mkdirp(sockPath, '0755', function (err) {
82 return err ? callback(err) : callback(null, []);
83 });
84 }
85
86 callback(null, sockets);
87}
88
89//
90// ### function getAllProcess (callback)
91// #### @callback {function} Continuation to respond to when complete.
92// Returns all data for processes managed by forever.
93//
94function getAllProcesses(callback) {
95 var sockPath = forever.config.get('sockPath');
96
97 function getProcess(name, next) {
98 var fullPath = path.join(sockPath, name),
99 socket = new nssocket.NsSocket();
100
101 if (process.platform === 'win32') {
102 // it needs the prefix
103 fullPath = '\\\\.\\pipe\\' + fullPath;
104 }
105
106 socket.connect(fullPath, function (err) {
107 if (err) {
108 next(err);
109 }
110
111 socket.dataOnce(['data'], function (data) {
112 data.socket = fullPath;
113 next(null, data);
114 socket.end();
115 });
116
117 socket.send(['data']);
118 });
119
120 socket.on('error', function (err) {
121 if (err.code === 'ECONNREFUSED') {
122 fs.unlink(fullPath, function () {
123 next();
124 });
125 }
126 else {
127 next();
128 }
129 });
130 }
131
132 getSockets(sockPath, function (err, sockets) {
133 if (err || (sockets && sockets.length === 0)) {
134 return callback(err);
135 }
136
137 async.map(sockets, getProcess, function (err, processes) {
138 callback(err, processes.filter(Boolean));
139 });
140 });
141}
142
143//
144// ### function getAllPids ()
145// Returns the set of all pids managed by forever.
146// e.x. [{ pid: 12345, foreverPid: 12346 }, ...]
147//
148function getAllPids(processes) {
149 return !processes ? null : processes.map(function (proc) {
150 return {
151 pid: proc.pid,
152 foreverPid: proc.foreverPid
153 };
154 });
155}
156
157//
158// ### function stopOrRestart (action, event, format, target)
159// #### @action {string} Action that is to be sent to target(s).
160// #### @event {string} Event that will be emitted on success.
161// #### @format {boolean} Indicated if we should CLI format the returned output.
162// #### @target {string} Index or script name to stop. Optional.
163// #### If not provided -> action will be sent to all targets.
164// Returns emitter that you can use to handle events on failure or success (i.e 'error' or <event>)
165//
166function stopOrRestart(action, event, format, target) {
167 var emitter = new events.EventEmitter();
168
169 function sendAction(proc, next) {
170 var socket = new nssocket.NsSocket();
171
172 function onMessage(data) {
173 //
174 // Cleanup the socket.
175 //
176 socket.undata([action, 'ok'], onMessage);
177 socket.undata([action, 'error'], onMessage);
178 socket.end();
179
180 //
181 // Messages are only sent back from error cases. The event
182 // calling context is available from `nssocket`.
183 //
184 var message = data && data.message,
185 type = this.event.slice().pop();
186
187 //
188 // Remark (Tjatse): This message comes from `forever-monitor`, the process is marked
189 // as `STOPPED`: message: Cannot stop process that is not running.
190 //
191 // Remark (indexzero): We should probably warn instead of emitting an error in `forever-monitor`,
192 // OR handle that error in `bin/worker` for better RPC.
193 //
194 return type === 'error' && /is not running/.test(message)
195 ? next(new Error(message))
196 : next(null, data);
197 }
198
199 socket.connect(proc.socket, function (err) {
200 if (err) {
201 next(err);
202 }
203
204 socket.dataOnce([action, 'ok'], onMessage);
205 socket.dataOnce([action, 'error'], onMessage);
206 socket.send([action]);
207 });
208
209 //
210 // Remark (indexzero): This is a race condition, but unlikely to ever hit since
211 // if the socket errors we will never get any data in the first place...
212 //
213 socket.on('error', function (err) {
214 next(err);
215 });
216 }
217
218
219 getAllProcesses(function (err, processes) {
220 if (err) {
221 return process.nextTick(function () {
222 emitter.emit('error', err);
223 });
224 }
225
226 var procs;
227 if (target !== undefined && target !== null) {
228 if (isNaN(target)) {
229 procs = forever.findByScript(target, processes);
230 }
231 procs = procs
232 || forever.findById(target, processes)
233 || forever.findByIndex(target, processes)
234 || forever.findByUid(target, processes)
235 || forever.findByPid(target, processes);
236 }
237 else {
238 procs = processes;
239 }
240
241 if (procs && procs.length > 0) {
242 async.map(procs, sendAction, function (err, results) {
243 if (err) {
244 emitter.emit('error', err);
245 }
246
247 //
248 // Remark (indexzero): we should do something with the results.
249 //
250 emitter.emit(event, forever.format(format, procs));
251 });
252 }
253 else {
254 process.nextTick(function () {
255 emitter.emit('error', new Error('Cannot find forever process: ' + target));
256 });
257 }
258 });
259
260 return emitter;
261}
262
263//
264// ### function load (options, [callback])
265// #### @options {Object} Options to load into the forever module
266// Initializes configuration for forever module
267//
268forever.load = function (options) {
269 // memorize current options.
270 this._loadedOptions = options;
271
272 //
273 // Setup the incoming options with default options.
274 //
275 options = options || {};
276 options.loglength = options.loglength || 100;
277 options.logstream = options.logstream || false;
278 options.root = options.root || forever.root;
279 options.pidPath = options.pidPath || path.join(options.root, 'pids');
280 options.sockPath = options.sockPath || path.join(options.root, 'sock');
281
282 //
283 // If forever is initalized and the config directories are identical
284 // simply return without creating directories
285 //
286 if (forever.initialized && forever.config.get('root') === options.root &&
287 forever.config.get('pidPath') === options.pidPath) {
288 return;
289 }
290
291 forever.config = new nconf.File({ file: path.join(options.root, 'config.json') });
292
293 //
294 // Try to load the forever `config.json` from
295 // the specified location.
296 //
297 try {
298 forever.config.loadSync();
299 }
300 catch (ex) { }
301
302 //
303 // Setup the columns for `forever list`.
304 //
305 options.columns = options.columns || forever.config.get('columns');
306 if (!options.columns) {
307 options.columns = [
308 'uid', 'command', 'script', 'forever', 'pid', 'id', 'logfile', 'uptime'
309 ];
310 }
311
312 forever.config.set('root', options.root);
313 forever.config.set('pidPath', options.pidPath);
314 forever.config.set('sockPath', options.sockPath);
315 forever.config.set('loglength', options.loglength);
316 forever.config.set('logstream', options.logstream);
317 forever.config.set('columns', options.columns);
318
319 //
320 // Setup timestamp to event logger
321 //
322 forever.out.transports.console.timestamp = forever.config.get('timestamp') === 'true';
323
324 //
325 // Attempt to see if `forever` has been configured to
326 // run in debug mode.
327 //
328 options.debug = options.debug || forever.config.get('debug') || false;
329
330 if (options.debug) {
331 //
332 // If we have been indicated to debug this forever process
333 // then setup `forever._debug` to be an instance of `winston.Logger`.
334 //
335 forever._debug();
336 }
337
338 //
339 // Syncronously create the `root` directory
340 // and the `pid` directory for forever. Although there is
341 // an additional overhead here of the sync action. It simplifies
342 // the setup of forever dramatically.
343 //
344 function tryCreate(dir) {
345 try {
346 fs.mkdirSync(dir, '0755');
347 }
348 catch (ex) { }
349 }
350
351 tryCreate(forever.config.get('root'));
352 tryCreate(forever.config.get('pidPath'));
353 tryCreate(forever.config.get('sockPath'));
354
355 //
356 // Attempt to save the new `config.json` for forever
357 //
358 try {
359 forever.config.saveSync();
360 }
361 catch (ex) { }
362
363 forever.initialized = true;
364};
365
366//
367// ### @private function _debug ()
368// Sets up debugging for this forever process
369//
370forever._debug = function () {
371 var debug = forever.config.get('debug');
372
373 if (!debug) {
374 forever.config.set('debug', true);
375 forever.log.add(winston.transports.File, {
376 level: 'silly',
377 filename: path.join(forever.config.get('root'), 'forever.debug.log')
378 });
379 }
380};
381
382//
383// Ensure forever will always be loaded the first time it is required.
384//
385forever.load();
386
387//
388// ### function stat (logFile, script, callback)
389// #### @logFile {string} Path to the log file for this script
390// #### @logAppend {boolean} Optional. True Prevent failure if the log file exists.
391// #### @script {string} Path to the target script.
392// #### @callback {function} Continuation to pass control back to
393// Ensures that the logFile doesn't exist and that
394// the target script does exist before executing callback.
395//
396forever.stat = function (logFile, script, callback) {
397 var logAppend;
398
399 if (arguments.length === 4) {
400 logAppend = callback;
401 callback = arguments[3];
402 }
403
404 fs.stat(script, function (err, stats) {
405 if (err) {
406 return callback(new Error('script ' + script + ' does not exist.'));
407 }
408
409 return logAppend ? callback(null) : fs.stat(logFile, function (err, stats) {
410 return !err
411 ? callback(new Error('log file ' + logFile + ' exists. Use the -a or --append option to append log.'))
412 : callback(null);
413 });
414 });
415};
416
417//
418// ### function start (script, options)
419// #### @script {string} Location of the script to run.
420// #### @options {Object} Configuration for forever instance.
421// Starts a script with forever
422//
423forever.start = function (script, options) {
424 if (!options.uid) {
425 options.uid = utile.randomString(4).replace(/^\-/, '_');
426 }
427
428 if (!options.logFile) {
429 options.logFile = forever.logFilePath(options.uid + '.log');
430 }
431
432 //
433 // Create the monitor, log events, and start.
434 //
435 var monitor = new forever.Monitor(script, options);
436 forever.logEvents(monitor);
437 return monitor.start();
438};
439
440//
441// ### function startDaemon (script, options)
442// #### @script {string} Location of the script to run.
443// #### @options {Object} Configuration for forever instance.
444// Starts a script with forever as a daemon
445//
446forever.startDaemon = function (script, options) {
447 options = options || {};
448 options.uid = options.uid || utile.randomString(4).replace(/^\-/, '_');
449 options.logFile = forever.logFilePath(options.logFile || forever.config.get('logFile') || options.uid + '.log');
450 options.pidFile = forever.pidFilePath(options.pidFile || forever.config.get('pidFile') || options.uid + '.pid');
451
452 var monitor, outFD, errFD, monitorPath;
453
454 //
455 // This log file is forever's log file - the user's outFile and errFile
456 // options are not taken into account here. This will be an aggregate of all
457 // the app's output, as well as messages from the monitor process, where
458 // applicable.
459 //
460 outFD = fs.openSync(options.logFile, 'a');
461 errFD = fs.openSync(options.logFile, 'a');
462 monitorPath = path.resolve(__dirname, '..', 'bin', 'monitor');
463
464 monitor = spawn(process.execPath, [monitorPath, script], {
465 stdio: ['ipc', outFD, errFD],
466 detached: true
467 });
468
469 monitor.on('exit', function (code) {
470 console.error('Monitor died unexpectedly with exit code %d', code);
471 });
472
473 // transmit options to daemonic(child) process, keep configuration lineage.
474 options._loadedOptions = this._loadedOptions;
475
476 monitor.send(JSON.stringify(options));
477
478 // close the ipc communication channel with the monitor
479 // otherwise the corresponding events listeners will prevent
480 // the exit of the current process (observed with node 0.11.9)
481 monitor.disconnect();
482
483 // make sure the monitor is unref() and does not prevent the
484 // exit of the current process
485 monitor.unref();
486
487 return monitor;
488};
489
490//
491// ### function startServer ()
492// #### @arguments {forever.Monitor...} A list of forever.Monitor instances
493// Starts the `forever` HTTP server for communication with the forever CLI.
494// **NOTE:** This will change your `process.title`.
495//
496forever.startServer = function () {
497 var args = Array.prototype.slice.call(arguments),
498 monitors = [],
499 callback;
500
501 args.forEach(function (a) {
502 if (Array.isArray(a)) {
503 monitors = monitors.concat(a.filter(function (m) {
504 return m instanceof forever.Monitor;
505 }));
506 }
507 else if (a instanceof forever.Monitor) {
508 monitors.push(a);
509 }
510 else if (typeof a === 'function') {
511 callback = a;
512 }
513 });
514
515 async.map(monitors, function (monitor, next) {
516 var worker = new forever.Worker({
517 monitor: monitor,
518 sockPath: forever.config.get('sockPath'),
519 exitOnStop: true
520 });
521
522 worker.start(function (err) {
523 return err ? next(err) : next(null, worker);
524 });
525 }, callback || function () {});
526};
527
528
529//
530// ### function stop (target, [format])
531// #### @target {string} Index or script name to stop
532// #### @format {boolean} Indicated if we should CLI format the returned output.
533// Stops the process(es) with the specified index or script name
534// in the list of all processes
535//
536forever.stop = function (target, format) {
537 return stopOrRestart('stop', 'stop', format, target);
538};
539
540//
541// ### function restart (target, format)
542// #### @target {string} Index or script name to restart
543// #### @format {boolean} Indicated if we should CLI format the returned output.
544// Restarts the process(es) with the specified index or script name
545// in the list of all processes
546//
547forever.restart = function (target, format) {
548 return stopOrRestart('restart', 'restart', format, target);
549};
550
551//
552// ### function stopbypid (target, format)
553// #### @pid {string} Pid of process to stop.
554// #### @format {boolean} Indicated if we should CLI format the returned output.
555// Stops the process with specified pid
556//
557forever.stopbypid = function (pid, format) {
558 // stopByPid only capable of stopping, but can't restart
559 return stopOrRestart('stop', 'stopByPid', format, pid);
560};
561
562//
563// ### function restartAll (format)
564// #### @format {boolean} Value indicating if we should format output
565// Restarts all processes managed by forever.
566//
567forever.restartAll = function (format) {
568 return stopOrRestart('restart', 'restartAll', format);
569};
570
571//
572// ### function stopAll (format)
573// #### @format {boolean} Value indicating if we should format output
574// Stops all processes managed by forever.
575//
576forever.stopAll = function (format) {
577 return stopOrRestart('stop', 'stopAll', format);
578};
579
580//
581// ### function list (format, procs, callback)
582// #### @format {boolean} If set, will return a formatted string of data
583// #### @callback {function} Continuation to respond to when complete.
584// Returns the list of all process data managed by forever.
585//
586forever.list = function (format, callback) {
587 getAllProcesses(function (err, processes) {
588 callback(err, forever.format(format, processes));
589 });
590};
591
592//
593// ### function tail (target, length, callback)
594// #### @target {string} Target script to list logs for
595// #### @options {length|stream} **Optional** Length of the logs to tail, boolean stream
596// #### @callback {function} Continuation to respond to when complete.
597// Responds with the latest `length` logs for the specified `target` process
598// managed by forever. If no `length` is supplied then `forever.config.get('loglength`)`
599// is used.
600//
601forever.tail = function (target, options, callback) {
602 if (!callback && typeof options === 'function') {
603 callback = options;
604 options.length = 0;
605 options.stream = false;
606 }
607
608 var that = this,
609 length = options.length || forever.config.get('loglength'),
610 stream = options.stream || forever.config.get('logstream'),
611 blanks = function (e, i, a) { return e !== ''; },
612 title = function (e, i, a) { return e.match(/^==>/); },
613 args = ['-n', length],
614 logs;
615
616 if (stream) { args.unshift('-f'); }
617
618 function tailProcess(procs, next) {
619 var count = 0,
620 map = {},
621 tail;
622
623 procs.forEach(function (proc) {
624 args.push(proc.logFile);
625 map[proc.logFile] = { pid: proc.pid, file: proc.file };
626 count++;
627 });
628
629 tail = spawn('tail', args, {
630 stdio: [null, 'pipe', 'pipe'],
631 });
632
633 tail.stdio[1].setEncoding('utf8');
634 tail.stdio[2].setEncoding('utf8');
635
636 tail.stdio[1].on('data', function (data) {
637 var chunk = data.split('\n\n');
638 chunk.forEach(function (logs) {
639 var logs = logs.split('\n').filter(blanks),
640 file = logs.filter(title),
641 lines,
642 proc;
643
644 proc = file.length
645 ? map[file[0].split(' ')[1]]
646 : map[procs[0].logFile];
647
648 lines = count !== 1
649 ? logs.slice(1)
650 : logs;
651
652 lines.forEach(function (line) {
653 callback(null, { file: proc.file, pid: proc.pid, line: line });
654 });
655 });
656 });
657
658 tail.stdio[2].on('data', function (err) {
659 return callback(err);
660 });
661 }
662
663 getAllProcesses(function (err, processes) {
664 if (err) {
665 return callback(err);
666 }
667 else if (!processes) {
668 return callback(new Error('Cannot find forever process: ' + target));
669 }
670
671 var procs = forever.findByIndex(target, processes)
672 || forever.findByScript(target, processes);
673
674 if (!procs) {
675 return callback(new Error('No logs available for process: ' + target));
676 }
677
678 tailProcess(procs, callback);
679 });
680};
681
682//
683// ### function findById (id, processes)
684// #### @index {string} Index of the process to find.
685// #### @processes {Array} Set of processes to find in.
686// Finds the process with the specified index.
687//
688forever.findById = function (id, processes) {
689 if (!processes) { return null; }
690
691 var procs = processes.filter(function (p) {
692 return p.id == id;
693 });
694
695 if (procs.length === 0) { procs = null; }
696 return procs;
697};
698
699//
700// ### function findByIndex (index, processes)
701// #### @index {string} Index of the process to find.
702// #### @processes {Array} Set of processes to find in.
703// Finds the process with the specified index.
704//
705forever.findByIndex = function (index, processes) {
706 var indexAsNum = parseInt(index, 10),
707 proc;
708
709 if (indexAsNum == index) {
710 proc = processes && processes[indexAsNum];
711 }
712 return proc ? [proc] : null;
713};
714
715//
716// ### function findByScript (script, processes)
717// #### @script {string} The name of the script to find.
718// #### @processes {Array} Set of processes to find in.
719// Finds the process with the specified script name.
720//
721forever.findByScript = function (script, processes) {
722 if (!processes) { return null; }
723
724 // make script absolute.
725 if (script.indexOf('/') != 0) {
726 script = path.resolve(process.cwd(), script);
727 }
728
729 var procs = processes.filter(function (p) {
730 return p.file === script || path.join(p.spawnWith.cwd, p.file) === script;
731 });
732
733 if (procs.length === 0) { procs = null; }
734 return procs;
735};
736
737//
738// ### function findByUid (uid, processes)
739// #### @uid {string} The uid of the process to find.
740// #### @processes {Array} Set of processes to find in.
741// Finds the process with the specified uid.
742//
743forever.findByUid = function (script, processes) {
744 var procs = !processes
745 ? null
746 : processes.filter(function (p) {
747 return p.uid === script;
748 });
749
750 if (procs && procs.length === 0) { procs = null; }
751 return procs;
752};
753
754//
755// ### function findByPid (pid, processes)
756// #### @pid {string} The pid of the process to find.
757// #### @processes {Array} Set of processes to find in.
758// Finds the process with the specified pid.
759//
760forever.findByPid = function (pid, processes) {
761 pid = typeof pid === 'string'
762 ? parseInt(pid, 10)
763 : pid;
764
765 var procs = processes && processes.filter(function (p) {
766 return p.pid === pid;
767 });
768
769 if (procs && procs.length === 0) { procs = null; }
770 return procs || null;
771};
772
773//
774// ### function format (format, procs)
775// #### @format {Boolean} Value indicating if processes should be formatted
776// #### @procs {Array} Processes to format
777// Returns a formatted version of the `procs` supplied based on the column
778// configuration in `forever.config`.
779//
780forever.format = function (format, procs) {
781 if (!procs || procs.length === 0) {
782 return null;
783 }
784
785 var index = 0,
786 columns = forever.config.get('columns'),
787 rows = [[' '].concat(columns)],
788 formatted;
789
790 function mapColumns(prefix, mapFn) {
791 return [prefix].concat(columns.map(mapFn));
792 }
793
794 if (format) {
795 //
796 // Iterate over the procs to see which has the
797 // longest options string
798 //
799 procs.forEach(function (proc) {
800 rows.push(mapColumns('[' + index + ']', function (column) {
801 return forever.columns[column]
802 ? forever.columns[column].get(proc)
803 : 'MISSING';
804 }));
805
806 index++;
807 });
808
809 formatted = cliff.stringifyRows(rows, mapColumns('white', function (column) {
810 return forever.columns[column]
811 ? forever.columns[column].color
812 : 'white';
813 }));
814 }
815
816 return format ? formatted : procs;
817};
818
819//
820// ### function cleanUp ()
821// Utility function for removing excess pid and
822// config, and log files used by forever.
823//
824forever.cleanUp = function (cleanLogs, allowManager) {
825 var emitter = new events.EventEmitter(),
826 pidPath = forever.config.get('pidPath');
827
828 getAllProcesses(function (err, processes) {
829 if (err) {
830 return process.nextTick(function () {
831 emitter.emit('error', err);
832 });
833 }
834 else if (cleanLogs) {
835 forever.cleanLogsSync(processes);
836 }
837
838 function unlinkProcess(proc, done) {
839 fs.unlink(path.join(pidPath, proc.uid + '.pid'), function () {
840 //
841 // Ignore errors (in case the file doesnt exist).
842 //
843
844 if (cleanLogs && proc.logFile) {
845 //
846 // If we are cleaning logs then do so if the process
847 // has a logfile.
848 //
849 return fs.unlink(proc.logFile, function () {
850 done();
851 });
852 }
853
854 done();
855 });
856 }
857
858 function cleanProcess(proc, done) {
859 if (proc.child && proc.manager) {
860 return done();
861 }
862 else if (!proc.child && !proc.manager
863 || (!proc.child && proc.manager && allowManager)
864 || proc.dead) {
865 return unlinkProcess(proc, done);
866 }
867
868 //
869 // If we have a manager but no child, wait a moment
870 // in-case the child is currently restarting, but **only**
871 // if we have not already waited for this process
872 //
873 if (!proc.waited) {
874 proc.waited = true;
875 return setTimeout(function () {
876 checkProcess(proc, done);
877 }, 500);
878 }
879
880 done();
881 }
882
883 function checkProcess(proc, next) {
884 proc.child = forever.checkProcess(proc.pid);
885 proc.manager = forever.checkProcess(proc.foreverPid);
886 cleanProcess(proc, next);
887 }
888
889 if (processes && processes.length > 0) {
890 (function cleanBatch(batch) {
891 async.forEach(batch, checkProcess, function () {
892 return processes.length > 0
893 ? cleanBatch(processes.splice(0, 10))
894 : emitter.emit('cleanUp');
895 });
896 })(processes.splice(0, 10));
897 }
898 else {
899 process.nextTick(function () {
900 emitter.emit('cleanUp');
901 });
902 }
903 });
904
905 return emitter;
906};
907
908//
909// ### function cleanLogsSync (processes)
910// #### @processes {Array} The set of all forever processes
911// Removes all log files from the root forever directory
912// that do not belong to current running forever processes.
913//
914forever.cleanLogsSync = function (processes) {
915 var root = forever.config.get('root'),
916 files = fs.readdirSync(root),
917 running,
918 runningLogs;
919
920 running = processes && processes.filter(function (p) {
921 return p && p.logFile;
922 });
923
924 runningLogs = running && running.map(function (p) {
925 return p.logFile.split('/').pop();
926 });
927
928 files.forEach(function (file) {
929 if (/\.log$/.test(file) && (!runningLogs || runningLogs.indexOf(file) === -1)) {
930 fs.unlinkSync(path.join(root, file));
931 }
932 });
933};
934
935//
936// ### function logFilePath (logFile)
937// #### @logFile {string} Log file path
938// Determines the full logfile path name
939//
940forever.logFilePath = function (logFile, uid) {
941 return logFile && (logFile[0] === '/' || logFile[1] === ':')
942 ? logFile
943 : path.join(forever.config.get('root'), logFile || (uid || 'forever') + '.log');
944};
945
946//
947// ### function pidFilePath (pidFile)
948// #### @logFile {string} Pid file path
949// Determines the full pid file path name
950//
951forever.pidFilePath = function (pidFile) {
952 return pidFile && (pidFile[0] === '/' || pidFile[1] === ':')
953 ? pidFile
954 : path.join(forever.config.get('pidPath'), pidFile);
955};
956
957//
958// ### @function logEvents (monitor)
959// #### @monitor {forever.Monitor} Monitor to log events for
960// Logs important restart and error events to `console.error`
961//
962forever.logEvents = function (monitor) {
963 monitor.on('watch:error', function (info) {
964 forever.out.error(info.message);
965 forever.out.error(info.error);
966 });
967
968 monitor.on('watch:restart', function (info) {
969 forever.out.error('restarting script because ' + info.file + ' changed');
970 });
971
972 monitor.on('restart', function () {
973 forever.out.error('Script restart attempt #' + monitor.times);
974 });
975
976 monitor.on('exit:code', function (code, signal) {
977 forever.out.error((code !== null && code !== undefined)
978 ? 'Forever detected script exited with code: ' + code
979 : 'Forever detected script was killed by signal: ' + signal);
980 });
981};
982
983//
984// ### @columns {Object}
985// Property descriptors for accessing forever column information
986// through `forever list` and `forever.list()`
987//
988forever.columns = {
989 uid: {
990 color: 'white',
991 get: function (proc) {
992 return proc.uid;
993 }
994 },
995 id: {
996 color: 'white',
997 get: function (proc) {
998 return proc.id ? proc.id : '';
999 }
1000 },
1001 command: {
1002 color: 'grey',
1003 get: function (proc) {
1004 return (proc.command || 'node').grey;
1005 }
1006 },
1007 script: {
1008 color: 'grey',
1009 get: function (proc) {
1010 return [proc.file].concat(proc.args).join(' ').grey;
1011 }
1012 },
1013 forever: {
1014 color: 'white',
1015 get: function (proc) {
1016 return proc.foreverPid;
1017 }
1018 },
1019 pid: {
1020 color: 'white',
1021 get: function (proc) {
1022 return proc.pid;
1023 }
1024 },
1025 logfile: {
1026 color: 'magenta',
1027 get: function (proc) {
1028 return proc.logFile ? proc.logFile.magenta : '';
1029 }
1030 },
1031 dir: {
1032 color: 'grey',
1033 get: function (proc) {
1034 return proc.sourceDir.grey;
1035 }
1036 },
1037 uptime: {
1038 color: 'yellow',
1039 get: function (proc) {
1040 return proc.running ? timespan.fromDates(new Date(proc.ctime), new Date()).toString().yellow : "STOPPED".red;
1041 }
1042 }
1043};