diff --git a/bin/pm2 b/bin/pm2 index 334b60f37..3f6a6e735 100755 --- a/bin/pm2 +++ b/bin/pm2 @@ -487,9 +487,14 @@ commander.command('reloadLogs') // Log streaming // commander.command('logs [id|name]') + .option('--lines ', 'output the last N lines, instead of the last 20') .description('stream logs file. Default stream all logs') - .action(function(id) { - CLI.streamLogs(id); + .action(function(id, cmd) { + var line = 20; + if(typeof cmd.lines == 'number'){ + line = cmd.lines; + } + CLI.streamLogs(id, line); }); commander.command('ilogs') diff --git a/lib/CLI.js b/lib/CLI.js index 2907408ec..054cc6371 100644 --- a/lib/CLI.js +++ b/lib/CLI.js @@ -1239,40 +1239,41 @@ CLI.monit = function(cb) { * @param {} id * @return */ -function fallbackLogStream(id) { +function fallbackLogStream(id, lines) { + lines = lines || 20; Satan.executeRemote('getMonitorData', {}, function(err, list) { if (err) { printError(err); exitCli(cst.ERROR_EXIT); } - Log.stream(cst.PM2_LOG_FILE_PATH, 'PM2'); + Log.stream(cst.PM2_LOG_FILE_PATH, lines); printOut('########### Starting streaming logs for [%s] process', id || 'all'); list.forEach(function(proc) { if ((!id || (id && !isNaN(parseInt(id)) && proc.pm_id == id)) || (!id || (id && isNaN(parseInt(id)) && proc.pm2_env.name == id))) { - var app_name = proc.pm2_env.name || p.basename(proc.pm2_env.pm_exec_path); - - if (proc.pm2_env.pm_log_path) { - Log.stream(proc.pm2_env.pm_log_path, - app_name + '-' + proc.pm_id + ' (entire)'); - } else { - if (proc.pm2_env.pm_out_log_path) - Log.stream(proc.pm2_env.pm_out_log_path, - app_name + '-' + proc.pm_id + ' (out)'); - if (proc.pm2_env.pm_err_log_path) - Log.stream(proc.pm2_env.pm_err_log_path, - app_name + '-' + proc.pm_id + ' (err)'); - } + var app_name = (proc.pm2_env.name || p.basename(proc.pm2_env.pm_exec_path)) + '-' + proc.pm_id; + + ['', 'out', 'err'].some(function(n){ + var pk = 'pm_' + (n ? n + '_':'') + 'log_path'; + if(pk in proc.pm2_env){ + Log.stream({ + path: proc.pm2_env[pk], + type: n + }, app_name + '(' + (!n ? 'entire' : n) + ')', lines); + return !n; + } + return false; + }); } }); }); } -CLI.streamLogs = function(id) { - fallbackLogStream(id); +CLI.streamLogs = function(id, lines) { + fallbackLogStream(id, lines); }; CLI.ilogs = function() { @@ -1474,7 +1475,7 @@ function speedList() { if (Satan._noDaemonMode) { printOut('--no-daemon option enabled = do not exit pm2 CLI'); printOut('PM2 dameon PID = %s', fs.readFileSync(cst.PM2_PID_FILE_PATH)); - return Log.stream(cst.PM2_LOG_FILE_PATH, 'PM2', 0); + return Log.stream(cst.PM2_LOG_FILE_PATH); } else { return exitCli(cst.SUCCESS_EXIT); diff --git a/lib/Log.js b/lib/Log.js index 4867c24a8..3cefc5c57 100644 --- a/lib/Log.js +++ b/lib/Log.js @@ -1,93 +1,78 @@ -// -// Display a file in streaming -// -var fs = require('fs'); +var fs = require('fs'), + chalk = require('chalk'), + spawn = require('child_process').spawn; -var colors = [ - '\x1B[34m', // blue - '\x1B[36m', // cyan - '\x1B[32m', // green - '\x1B[35m', // magenta - '\x1B[31m', // red - '\x1B[90m', // grey - '\x1B[33m' // yellow -]; +var Log = module.exports = {}; -var gl_idx = 0; -var db = []; +// Empty line. +var re_blank = /^[\s\r\t]*$/; -var Log = module.exports = {}; +/** + * Styles of title and content. + * @type {{def: {title: string, content: string}, out: {title: string, content: string}, err: {title: string, content: string}}} + */ +Log.styles = { + def: {title: 'blue', content: 'grey'}, + out: {title: 'green', content: 'black'}, + err: {title: 'red', content: 'red'} +}; /** - * Description - * @method stream - * @param {} path - * @param {} title - * @return + * Tail logs from file stream. + * @param {String|Object} path + * @param {String} title + * @param {Number} lines + * @returns {*} */ -Log.stream = function(path, title, read_bytes) { - if (title === undefined) - title = gl_idx; +Log.stream = function(path, title, lines){ + var type = 'def'; - try { - var currSize = fs.statSync(path).size - (typeof(read_bytes) === 'undefined' ? 1000 : read_bytes); - currSize = currSize > 0 ? currSize : 0; - } catch(e) { - if (e.code == 'ENOENT') - console.log('%s with %s file not found', title, path); - return false; + // Make options in the right position. + if (typeof path == 'object') { + type = !path.type ? 'def' : path.type; + path = path.path; } + if (typeof title == 'number') { + lines = title; + title = null; + } + lines = lines || 20; - var odb = db[title] = {color : colors[gl_idx++ % colors.length], l : 0}; + title = '[' + (title || 'PM2') + ']'; - var _stream = function() { - fs.stat(path, function(err, stat) { - var prevSize = stat.size; + var style = Log.styles[type] || 'blue'; - if (currSize > prevSize) return true; + // Check file exist or not. + if (!fs.existsSync(path)) { + return console.info(chalk.bold.red(title), chalk.red('Log file "' + path + '" does not exist.')); + } - var rstream = fs.createReadStream(path, { - encoding : 'utf8', - start : currSize, - end : prevSize - }); + // Tail logs. + var tail = spawn('tail', ['-f', '-n', lines, path], { + // Kill the spawned process by `tail.kill('SIGTERM')`. + killSignal: 'SIGTERM', + stdio : [null, 'pipe', 'pipe'] + }); - rstream.on('data', function(data) { - print_data(odb, title, data); - }); + // Use utf8 encoding. + tail.stdio.forEach(function(stdio){ + stdio.setEncoding('utf8'); + }); - currSize = stat.size; - return true; + // stdout. + tail.stdout.on('data', function(data){ + data.split(/\n/).forEach(function(line){ + if (!re_blank.test(line)) { + console.info(chalk.bold[style.title](title), chalk[style.content](line)); + } }); - }; - - _stream(); - - fs.watch(path, function(ev, filename) { - if (ev == 'rename') - return console.error('Renaming file ?'); - - _stream(); - return true; }); -}; -// -// Privates -// -/** - * Description - * @method print_data - * @param {} odb - * @param {} title - * @param {} data - * @return - */ -function print_data(odb, title, data) { - var lines = data.split('\n'); - - lines.forEach(function(l) { - if (l) - console.log(odb.color + '[%s]\x1B[39m %s', title, l); + // handle error. + tail.stderr.on('data', function(data){ + tail.disconnect(); + console.info(chalk.bold.red(title), chalk.red(data.toString().replace(/\n/, ''))); }); -} + + return tail; +};