Log.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. /**
  2. * Copyright 2013-2022 the PM2 project authors. All rights reserved.
  3. * Use of this source code is governed by a license that
  4. * can be found in the LICENSE file.
  5. */
  6. var fs = require('fs'),
  7. util = require('util'),
  8. chalk = require('chalk'),
  9. forEachLimit = require('async/forEachLimit'),
  10. dayjs = require('dayjs');
  11. var Log = module.exports = {};
  12. var DEFAULT_PADDING = ' ';
  13. /**
  14. * Tail logs from file stream.
  15. * @param {Object} apps_list
  16. * @param {Number} lines
  17. * @param {Boolean} raw
  18. * @param {Function} callback
  19. * @return
  20. */
  21. Log.tail = function(apps_list, lines, raw, callback) {
  22. var that = this;
  23. if (lines === 0 || apps_list.length === 0)
  24. return callback && callback();
  25. var count = 0;
  26. var getLastLines = function (filename, lines, callback) {
  27. var chunk = '';
  28. var size = Math.max(0, fs.statSync(filename).size - (lines * 200));
  29. var fd = fs.createReadStream(filename, {start : size});
  30. fd.on('data', function(data) { chunk += data.toString(); });
  31. fd.on('end', function() {
  32. chunk = chunk.split('\n').slice(-(lines+1));
  33. chunk.pop();
  34. callback(chunk);
  35. });
  36. };
  37. apps_list.sort(function(a, b) {
  38. return (fs.existsSync(a.path) ? fs.statSync(a.path).mtime.valueOf() : 0) -
  39. (fs.existsSync(b.path) ? fs.statSync(b.path).mtime.valueOf() : 0);
  40. });
  41. forEachLimit(apps_list, 1, function(app, next) {
  42. if (!fs.existsSync(app.path || ''))
  43. return next();
  44. getLastLines(app.path, lines, function(output) {
  45. console.log(chalk.grey('%s last %d lines:'), app.path, lines);
  46. output.forEach(function(out) {
  47. if (raw)
  48. return app.type === 'err' ? console.error(out) : console.log(out);
  49. if (app.type === 'out')
  50. process.stdout.write(chalk.green(pad(DEFAULT_PADDING, app.app_name) + ' | '));
  51. else if (app.type === 'err')
  52. process.stdout.write(chalk.red(pad(DEFAULT_PADDING, app.app_name) + ' | '));
  53. else
  54. process.stdout.write(chalk.blue(pad(DEFAULT_PADDING, 'PM2') + ' | '));
  55. console.log(out);
  56. });
  57. if (output.length)
  58. process.stdout.write('\n');
  59. next();
  60. });
  61. }, function() {
  62. callback && callback();
  63. });
  64. };
  65. /**
  66. * Stream logs in realtime from the bus eventemitter.
  67. * @param {String} id
  68. * @param {Boolean} raw
  69. * @return
  70. */
  71. Log.stream = function(Client, id, raw, timestamp, exclusive, highlight) {
  72. var that = this;
  73. Client.launchBus(function(err, bus, socket) {
  74. socket.on('reconnect attempt', function() {
  75. if (global._auto_exit === true) {
  76. if (timestamp)
  77. process.stdout.write(chalk['dim'](chalk.grey(dayjs().format(timestamp) + ' ')));
  78. process.stdout.write(chalk.blue(pad(DEFAULT_PADDING, 'PM2') + ' | ') + '[[[ Target PM2 killed. ]]]');
  79. process.exit(0);
  80. }
  81. });
  82. var min_padding = 3
  83. bus.on('log:*', function(type, packet) {
  84. if (id !== 'all'
  85. && packet.process.name != id
  86. && packet.process.pm_id != id)
  87. return;
  88. if ((type === 'out' && exclusive === 'err')
  89. || (type === 'err' && exclusive === 'out')
  90. || (type === 'PM2' && exclusive !== false))
  91. return;
  92. var lines;
  93. if (typeof(packet.data) === 'string')
  94. lines = (packet.data || '').split('\n');
  95. else
  96. return;
  97. lines.forEach(function(line) {
  98. if (!line || line.length === 0) return;
  99. if (raw)
  100. return type === 'err' ? process.stderr.write(util.format(line) + '\n') : process.stdout.write(util.format(line) + '\n');
  101. if (timestamp)
  102. process.stdout.write(chalk['dim'](chalk.grey(dayjs().format(timestamp) + ' ')));
  103. var name = packet.process.pm_id + '|' + packet.process.name;
  104. if (name.length > min_padding)
  105. min_padding = name.length + 1
  106. if (type === 'out')
  107. process.stdout.write(chalk.green(pad(' '.repeat(min_padding), name) + ' | '));
  108. else if (type === 'err')
  109. process.stdout.write(chalk.red(pad(' '.repeat(min_padding), name) + ' | '));
  110. else if (!raw && (id === 'all' || id === 'PM2'))
  111. process.stdout.write(chalk.blue(pad(' '.repeat(min_padding), 'PM2') + ' | '));
  112. if (highlight)
  113. process.stdout.write(util.format(line).replace(highlight, chalk.bgBlackBright(highlight)) + '\n');
  114. else
  115. process.stdout.write(util.format(line)+ '\n');
  116. });
  117. });
  118. });
  119. };
  120. Log.devStream = function(Client, id, raw, timestamp, exclusive) {
  121. var that = this;
  122. Client.launchBus(function(err, bus) {
  123. setTimeout(function() {
  124. bus.on('process:event', function(packet) {
  125. if (packet.event == 'online')
  126. console.log(chalk.green('[rundev] App %s restarted'), packet.process.name);
  127. });
  128. }, 1000);
  129. var min_padding = 3
  130. bus.on('log:*', function(type, packet) {
  131. if (id !== 'all'
  132. && packet.process.name != id
  133. && packet.process.pm_id != id)
  134. return;
  135. if ((type === 'out' && exclusive === 'err')
  136. || (type === 'err' && exclusive === 'out')
  137. || (type === 'PM2' && exclusive !== false))
  138. return;
  139. if (type === 'PM2')
  140. return;
  141. var name = packet.process.pm_id + '|' + packet.process.name;
  142. var lines;
  143. if (typeof(packet.data) === 'string')
  144. lines = (packet.data || '').split('\n');
  145. else
  146. return;
  147. lines.forEach(function(line) {
  148. if (!line || line.length === 0) return;
  149. if (raw)
  150. return process.stdout.write(util.format(line) + '\n');
  151. if (timestamp)
  152. process.stdout.write(chalk['dim'](chalk.grey(dayjs().format(timestamp) + ' ')));
  153. var name = packet.process.name + '-' + packet.process.pm_id;
  154. if (name.length > min_padding)
  155. min_padding = name.length + 1
  156. if (type === 'out')
  157. process.stdout.write(chalk.green(pad(' '.repeat(min_padding), name) + ' | '));
  158. else if (type === 'err')
  159. process.stdout.write(chalk.red(pad(' '.repeat(min_padding), name) + ' | '));
  160. else if (!raw && (id === 'all' || id === 'PM2'))
  161. process.stdout.write(chalk.blue(pad(' '.repeat(min_padding), 'PM2') + ' | '));
  162. process.stdout.write(util.format(line) + '\n');
  163. });
  164. });
  165. });
  166. };
  167. Log.jsonStream = function(Client, id) {
  168. var that = this;
  169. Client.launchBus(function(err, bus) {
  170. if (err) console.error(err);
  171. bus.on('process:event', function(packet) {
  172. process.stdout.write(JSON.stringify({
  173. timestamp : dayjs(packet.at),
  174. type : 'process_event',
  175. status : packet.event,
  176. app_name : packet.process.name
  177. }));
  178. process.stdout.write('\n');
  179. });
  180. bus.on('log:*', function(type, packet) {
  181. if (id !== 'all'
  182. && packet.process.name != id
  183. && packet.process.pm_id != id)
  184. return;
  185. if (type === 'PM2')
  186. return;
  187. if (typeof(packet.data) == 'string')
  188. packet.data = packet.data.replace(/(\r\n|\n|\r)/gm,'');
  189. process.stdout.write(JSON.stringify({
  190. message : packet.data,
  191. timestamp : dayjs(packet.at),
  192. type : type,
  193. process_id : packet.process.pm_id,
  194. app_name : packet.process.name
  195. }));
  196. process.stdout.write('\n');
  197. });
  198. });
  199. };
  200. Log.formatStream = function(Client, id, raw, timestamp, exclusive, highlight) {
  201. var that = this;
  202. Client.launchBus(function(err, bus) {
  203. bus.on('log:*', function(type, packet) {
  204. if (id !== 'all'
  205. && packet.process.name != id
  206. && packet.process.pm_id != id)
  207. return;
  208. if ((type === 'out' && exclusive === 'err')
  209. || (type === 'err' && exclusive === 'out')
  210. || (type === 'PM2' && exclusive !== false))
  211. return;
  212. if (type === 'PM2' && raw)
  213. return;
  214. var name = packet.process.name + '-' + packet.process.pm_id;
  215. var lines;
  216. if (typeof(packet.data) === 'string')
  217. lines = (packet.data || '').split('\n');
  218. else
  219. return;
  220. lines.forEach(function(line) {
  221. if (!line || line.length === 0) return;
  222. if (!raw) {
  223. if (timestamp)
  224. process.stdout.write('timestamp=' + dayjs().format(timestamp) + ' ');
  225. if (packet.process.name === 'PM2')
  226. process.stdout.write('app=pm2 ');
  227. if (packet.process.name !== 'PM2')
  228. process.stdout.write('app=' + packet.process.name + ' id=' + packet.process.pm_id + ' ');
  229. if (type === 'out')
  230. process.stdout.write('type=out ');
  231. else if (type === 'err')
  232. process.stdout.write('type=error ');
  233. }
  234. process.stdout.write('message=');
  235. if (highlight)
  236. process.stdout.write(util.format(line).replace(highlight, chalk.bgBlackBright(highlight)) + '\n');
  237. else
  238. process.stdout.write(util.format(line) + '\n');
  239. });
  240. });
  241. });
  242. };
  243. function pad(pad, str, padLeft) {
  244. if (typeof str === 'undefined')
  245. return pad;
  246. if (padLeft) {
  247. return (pad + str).slice(-pad.length);
  248. } else {
  249. return (str + pad).substring(0, pad.length);
  250. }
  251. }