diff --git a/lib/CLI.js b/lib/CLI.js index 2ae79ebf..17477da7 100644 --- a/lib/CLI.js +++ b/lib/CLI.js @@ -83,6 +83,8 @@ CLI.start = function(script, opts, cb) { appConf.max_memory_restart = opts.maxMemoryRestart; if (opts.instances) appConf['instances'] = opts.instances; + if (opts.log) + appConf['log_file'] = opts.log; if (opts.error) appConf['error_file'] = opts.error; if (opts.output) diff --git a/lib/CliUx.js b/lib/CliUx.js index 26adf366..359bec72 100644 --- a/lib/CliUx.js +++ b/lib/CliUx.js @@ -79,6 +79,9 @@ UX.describeTable = function(process) { { 'uptime' : (pm2_env.pm_uptime && pm2_env.status == 'online') ? timeSince(pm2_env.pm_uptime) : 0 }, { 'created at' : created_at } ); + if('pm_log_path' in pm2_env){ + table.splice(6, 0, {'entire log path': pm2_env.pm_log_path}); + } console.log(table.toString()); if (pm2_env.versioning) { diff --git a/lib/Common.js b/lib/Common.js index f1773d68..f382b763 100644 --- a/lib/Common.js +++ b/lib/Common.js @@ -103,12 +103,17 @@ Common.resolveAppPaths = function(app, cwd, outputter) { if (fs.existsSync(app.pm_exec_path) == false) { return new Error('script not found : ' + app.pm_exec_path); } + if (app.log_file){ + app["pm_log_path"] = typeof app.log_file == 'boolean' ? + path.resolve(cst.DEFAULT_LOG_PATH, [formated_app_name, '.log'].join('')) + : path.resolve(cwd, app.log_file); + } + delete app.log_file; if (app.out_file) app["pm_out_log_path"] = path.resolve(cwd, app.out_file); else { app["pm_out_log_path"] = path.resolve(cst.DEFAULT_LOG_PATH, [formated_app_name, '-out.log'].join('')); - app.out_file = app["pm_out_log_path"]; } delete app.out_file; @@ -116,7 +121,6 @@ Common.resolveAppPaths = function(app, cwd, outputter) { app["pm_err_log_path"] = path.resolve(cwd, app.error_file); else { app["pm_err_log_path"] = path.resolve(cst.DEFAULT_LOG_PATH, [formated_app_name, '-err.log'].join('')); - app.error_file = app["pm_err_log_path"]; } delete app.error_file; diff --git a/lib/God.js b/lib/God.js index 038911c1..fb738b85 100644 --- a/lib/God.js +++ b/lib/God.js @@ -121,8 +121,10 @@ God.executeApp = function executeApp(env, cb) { // If merge option, dont separate the logs if (!env_copy['merge_logs']) { - env_copy.pm_out_log_path = env_copy.pm_out_log_path.replace(/-[0-9]+\.log$|\.log$/g, '-' + env_copy['pm_id'] + '.log'); - env_copy.pm_err_log_path = env_copy.pm_err_log_path.replace(/-[0-9]+\.log$|\.log$/g, '-' + env_copy['pm_id'] + '.log'); + ['', '_out', '_err'].forEach(function(k){ + var key = 'pm' + k + '_log_path'; + env_copy[key] && (env_copy[key] = env_copy[key].replace(/-[0-9]+\.log$|\.log$/g, '-' + env_copy['pm_id'] + '.log')); + }) } // Initiate watch file diff --git a/lib/God/ForkMode.js b/lib/God/ForkMode.js index 2073eaa5..0af83c95 100644 --- a/lib/God/ForkMode.js +++ b/lib/God/ForkMode.js @@ -8,6 +8,7 @@ var log = require('debug')('pm2:god'); var fs = require('fs'); +var async = require('async'); var cst = require('../../constants.js'); var moment = require('moment'); var Common = require('../Common'); @@ -63,10 +64,15 @@ module.exports = function ForkMode(God) { args = args.concat(eval((pm2_env.args))); } - - var stdout, stderr; - var outFile = pm2_env.pm_out_log_path; - var errFile = pm2_env.pm_err_log_path; + // piping stream o file + var stds = { + out: pm2_env.pm_out_log_path, + err: pm2_env.pm_err_log_path + }; + // entire log std if necessary. + if('pm_log_path' in pm2_env){ + stds.std = pm2_env.pm_log_path; + } /** * Description @@ -75,24 +81,44 @@ module.exports = function ForkMode(God) { * @return */ function startLogging(cb) { - stdout = fs.createWriteStream(outFile, { flags : 'a' }); - - stdout.on('error', function(e) { - God.logAndGenerateError(e); - return cb(e); + // waterfall. + var flows = []; + // types of stdio, should be sorted as `std(entire log)`, `out`, `err`. + var types = Object.keys(stds).sort(function(x, y){ + return -x.charCodeAt(0) + y.charCodeAt(0); }); - stdout.on('open', function() { - stderr = fs.createWriteStream(errFile, { flags : 'a' }); + // Create write streams. + (function createWS(io){ + if(io.length != 1){ + return; + } + io = io[0]; - stderr.on('error', function(e) { - God.logAndGenerateError(e); - return cb(e); - }); + // If `std` is a Stream type, try next `std`. + // compatible with `pm2 reloadLogs` + if(typeof stds[io] == 'object' && !isNaN(stds[io].fd)){ + return createWS(types.splice(0, 1)); + } - stderr.on('open', function() { - return cb(null); + flows.push(function(next){ + var file = stds[io]; + stds[io] = fs.createWriteStream(file, {flags: 'a'}) + .on('error', function(e){ + next(e); + }).on('open', function(){ + next(); + }); + stds[io]._file = file; }); + createWS(types.splice(0, 1)); + })(types.splice(0, 1)); + + async.waterfall(flows, function(err, result){ + if(err){ + God.logAndGenerateError(err); + } + cb(err); }); } @@ -125,7 +151,8 @@ module.exports = function ForkMode(God) { if (pm2_env.log_date_format) log_data = moment().format(pm2_env.log_date_format) + ': ' + log_data; - stderr.write(log_data); + stds.err.write(log_data); + stds.std && stds.std.write(log_data); God.bus.emit('log:err', { process : Common.formatCLU(cspr), @@ -142,7 +169,8 @@ module.exports = function ForkMode(God) { if (pm2_env.log_date_format) log_data = moment().format(pm2_env.log_date_format) + ': ' + log_data; - stdout.write(log_data); + stds.out.write(log_data); + stds.std && stds.std.write(log_data); God.bus.emit('log:out', { process : Common.formatCLU(cspr), @@ -177,14 +205,18 @@ module.exports = function ForkMode(God) { cspr.once('close', function forkClose(status) { try { - stderr.close(); - stdout.close(); + for(var k in stds){ + stds[k].close(); + stds[k] = stds[k]._file; + } } catch(e) { God.logAndGenerateError(e);} }); cspr._reloadLogs = function(cb) { - stdout.close(); - stderr.close(); + for(var k in stds){ + stds[k].close(); + stds[k] = stds[k]._file; + } startLogging(cb); }; diff --git a/lib/ProcessContainer.js b/lib/ProcessContainer.js index 6dd39824..c25490f6 100644 --- a/lib/ProcessContainer.js +++ b/lib/ProcessContainer.js @@ -8,6 +8,7 @@ if (process.env.name != null) var fs = require('fs'); var p = require('path'); +var async = require('async'); var cst = require('../constants'); var axm = require('axm'); /** @@ -18,6 +19,7 @@ var axm = require('axm'); var fs = require('fs'); var worker = require('cluster').worker; + var stdFile = process.env.pm_log_path; var outFile = process.env.pm_out_log_path; var errFile = process.env.pm_err_log_path; var pmId = process.env.pm_id; @@ -41,8 +43,13 @@ var axm = require('axm'); if (process.env.args != null) process.argv = process.argv.concat(eval(process.env.args)); - - exec(script, outFile, errFile); + // stdio, including: out, err and entire (both out and err if necessary). + var stds = { + out: outFile, + err: errFile + }; + stdFile && (stds.std = stdFile); + exec(script, stds); if (cronRestart) cronize(cronRestart); @@ -78,21 +85,20 @@ function cronize(cron_pattern) { * Description * @method exec * @param {} script - * @param {} outFile - * @param {} errFile + * @param {} stds * @return */ -function exec(script, outFile, errFile) { - var stderr, stdout; - +function exec(script, stds) { if (p.extname(script) == '.coffee') { require('coffee-script/register'); } process.on('message', function (msg) { if (msg.type === 'log:reload') { - stdout.end(); - stderr.end(); + for(var k in stds){ + stds[k].close(); + stds[k] = stds[k]._file; + } startLogging(function () { console.log('Reloading log...'); }); @@ -112,53 +118,91 @@ function exec(script, outFile, errFile) { * @return */ function startLogging(callback) { - stdout = fs.createWriteStream(outFile, { flags : 'a' }); - - stdout.on('open', function() { - stderr = fs.createWriteStream(errFile, { flags : 'a' }); - stderr.on('open', function() { - - process.stderr.write = (function(write) { - return function(string, encoding, fd) { - var log_data = string.toString(); - if (process.env.log_date_format && moment) - log_data = moment().format(process.env.log_date_format) + ': ' + log_data; - stderr.write(log_data); - process.send({ - type : 'log:err', - data : string - }); - }; - } - )(process.stderr.write); - - process.stdout.write = (function(write) { - return function(string, encoding, fd) { - var log_data = string.toString(); - if (process.env.log_date_format && moment) - log_data = moment().format(process.env.log_date_format) + ': ' + log_data; - stdout.write(log_data); - process.send({ - type : 'log:out', - data : string - }); - }; - })(process.stdout.write); - return callback(); - }); + // waterfall. + var flows = []; + // types of stdio, should be sorted as `std(entire log)`, `out`, `err`. + var types = Object.keys(stds).sort(function(x, y){ + return -x.charCodeAt(0) + y.charCodeAt(0); }); + + // Create write streams. + (function createWS(io){ + if(io.length != 1){ + return; + } + io = io[0]; + + // If `std` is a Stream type, try next `std`. + // compatible with `pm2 reloadLogs` + if(typeof stds[io] == 'object' && !isNaN(stds[io].fd)){ + return createWS(types.splice(0, 1)); + } + + flows.push(function(next){ + var file = stds[io]; + stds[io] = fs.createWriteStream(file, {flags: 'a'}) + .on('error', function(e){ + next(e); + }).on('open', function(){ + next(); + }); + stds[io]._file = file; + }); + createWS(types.splice(0, 1)); + })(types.splice(0, 1)); + + async.waterfall(flows, callback); } - startLogging(function () { + startLogging(function (err) { + if(err){ + process.send({ + type : 'process:exception', + data : { + message: err.message, + syscall: 'ProcessContainer.startLogging' + } + }); + return; + } + process.stderr.write = (function(write) { + return function(string, encoding, fd) { + var log_data = string.toString(); + if (process.env.log_date_format && moment) + log_data = moment().format(process.env.log_date_format) + ': ' + log_data; + stds.err.write(log_data); + stds.std && stds.std.write(log_data); + process.send({ + type : 'log:err', + data : string + }); + }; + } + )(process.stderr.write); + + process.stdout.write = (function(write) { + return function(string, encoding, fd) { + var log_data = string.toString(); + if (process.env.log_date_format && moment) + log_data = moment().format(process.env.log_date_format) + ': ' + log_data; + stds.out.write(log_data); + stds.std && stds.std.write(log_data); + process.send({ + type : 'log:out', + data : string + }); + }; + })(process.stdout.write); process.on('uncaughtException', function uncaughtListener(err) { - try { - stderr.write(err.stack); - } catch(e) { + function logError(types, error){ try { - stderr.write(err.toString()); - } catch(e) {} + types.forEach(function(type){ + stds[type].write(error + '\n'); + }); + } catch(e) { } } + logError(['std', 'err'], err.stack); // Notify master that an uncaughtException has been catched try { @@ -174,9 +218,7 @@ function exec(script, outFile, errFile) { data : errObj }); } catch(e) { - try { - stderr.write('Channel is already closed can\'t broadcast error', err); - } catch(e) {} + logError(['std', 'err'], 'Channel is already closed can\'t broadcast error:\n' + e.stack); } if (!process.listeners('uncaughtException').filter(function (listener) {