bunyan 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652
  1. #!/usr/bin/env node
  2. /**
  3. * Copyright 2017 Trent Mick
  4. * Copyright 2017 Joyent Inc.
  5. *
  6. * bunyan -- filter and pretty-print Bunyan log files (line-delimited JSON)
  7. *
  8. * See <https://github.com/trentm/node-bunyan>.
  9. *
  10. * -*- mode: js -*-
  11. * vim: expandtab:ts=4:sw=4
  12. */
  13. var VERSION = '1.8.10';
  14. var p = console.log;
  15. var util = require('util');
  16. var pathlib = require('path');
  17. var vm = require('vm');
  18. var http = require('http');
  19. var fs = require('fs');
  20. var warn = console.warn;
  21. var child_process = require('child_process'),
  22. spawn = child_process.spawn,
  23. exec = child_process.exec,
  24. execFile = child_process.execFile;
  25. var assert = require('assert');
  26. try {
  27. var moment = require('moment');
  28. } catch (e) {
  29. moment = null;
  30. }
  31. //---- globals and constants
  32. var nodeVer = process.versions.node.split('.').map(Number);
  33. var nodeSpawnSupportsStdio = (nodeVer[0] > 0 || nodeVer[1] >= 8);
  34. // Internal debug logging via `console.warn`.
  35. var _DEBUG = false;
  36. // Output modes.
  37. var OM_LONG = 1;
  38. var OM_JSON = 2;
  39. var OM_INSPECT = 3;
  40. var OM_SIMPLE = 4;
  41. var OM_SHORT = 5;
  42. var OM_BUNYAN = 6;
  43. var OM_FROM_NAME = {
  44. 'long': OM_LONG,
  45. 'paul': OM_LONG, /* backward compat */
  46. 'json': OM_JSON,
  47. 'inspect': OM_INSPECT,
  48. 'simple': OM_SIMPLE,
  49. 'short': OM_SHORT,
  50. 'bunyan': OM_BUNYAN
  51. };
  52. // Levels
  53. var TRACE = 10;
  54. var DEBUG = 20;
  55. var INFO = 30;
  56. var WARN = 40;
  57. var ERROR = 50;
  58. var FATAL = 60;
  59. var levelFromName = {
  60. 'trace': TRACE,
  61. 'debug': DEBUG,
  62. 'info': INFO,
  63. 'warn': WARN,
  64. 'error': ERROR,
  65. 'fatal': FATAL
  66. };
  67. var nameFromLevel = {};
  68. var upperNameFromLevel = {};
  69. var upperPaddedNameFromLevel = {};
  70. Object.keys(levelFromName).forEach(function (name) {
  71. var lvl = levelFromName[name];
  72. nameFromLevel[lvl] = name;
  73. upperNameFromLevel[lvl] = name.toUpperCase();
  74. upperPaddedNameFromLevel[lvl] = (
  75. name.length === 4 ? ' ' : '') + name.toUpperCase();
  76. });
  77. // Display time formats.
  78. var TIME_UTC = 1; // the default, bunyan's native format
  79. var TIME_LOCAL = 2;
  80. // Timezone formats: output format -> momentjs format string
  81. var TIMEZONE_UTC_FORMATS = {
  82. long: '[[]YYYY-MM-DD[T]HH:mm:ss.SSS[Z][]]',
  83. short: 'HH:mm:ss.SSS[Z]'
  84. };
  85. var TIMEZONE_LOCAL_FORMATS = {
  86. long: '[[]YYYY-MM-DD[T]HH:mm:ss.SSSZ[]]',
  87. short: 'HH:mm:ss.SSS'
  88. };
  89. // The current raw input line being processed. Used for `uncaughtException`.
  90. var currLine = null;
  91. // Child dtrace process, if any. Used for signal-handling.
  92. var child = null;
  93. // Whether ANSI codes are being used. Used for signal-handling.
  94. var usingAnsiCodes = false;
  95. // Used to tell the 'uncaughtException' handler that '-c CODE' is being used.
  96. var gUsingConditionOpts = false;
  97. // Pager child process, and output stream to which to write.
  98. var pager = null;
  99. var stdout = process.stdout;
  100. //---- support functions
  101. function getVersion() {
  102. return VERSION;
  103. }
  104. var format = util.format;
  105. if (!format) {
  106. /* BEGIN JSSTYLED */
  107. // If not node 0.6, then use its `util.format`:
  108. // <https://github.com/joyent/node/blob/master/lib/util.js#L22>:
  109. var inspect = util.inspect;
  110. var formatRegExp = /%[sdj%]/g;
  111. format = function format(f) {
  112. if (typeof f !== 'string') {
  113. var objects = [];
  114. for (var i = 0; i < arguments.length; i++) {
  115. objects.push(inspect(arguments[i]));
  116. }
  117. return objects.join(' ');
  118. }
  119. var i = 1;
  120. var args = arguments;
  121. var len = args.length;
  122. var str = String(f).replace(formatRegExp, function (x) {
  123. if (i >= len)
  124. return x;
  125. switch (x) {
  126. case '%s': return String(args[i++]);
  127. case '%d': return Number(args[i++]);
  128. case '%j': return JSON.stringify(args[i++]);
  129. case '%%': return '%';
  130. default:
  131. return x;
  132. }
  133. });
  134. for (var x = args[i]; i < len; x = args[++i]) {
  135. if (x === null || typeof x !== 'object') {
  136. str += ' ' + x;
  137. } else {
  138. str += ' ' + inspect(x);
  139. }
  140. }
  141. return str;
  142. };
  143. /* END JSSTYLED */
  144. }
  145. function indent(s) {
  146. return ' ' + s.split(/\r?\n/).join('\n ');
  147. }
  148. function objCopy(obj) {
  149. if (obj === null) {
  150. return null;
  151. } else if (Array.isArray(obj)) {
  152. return obj.slice();
  153. } else {
  154. var copy = {};
  155. Object.keys(obj).forEach(function (k) {
  156. copy[k] = obj[k];
  157. });
  158. return copy;
  159. }
  160. }
  161. function printHelp() {
  162. /* BEGIN JSSTYLED */
  163. p('Usage:');
  164. p(' bunyan [OPTIONS] [FILE ...]');
  165. p(' ... | bunyan [OPTIONS]');
  166. p(' bunyan [OPTIONS] -p PID');
  167. p('');
  168. p('Filter and pretty-print Bunyan log file content.');
  169. p('');
  170. p('General options:');
  171. p(' -h, --help print this help info and exit');
  172. p(' --version print version of this command and exit');
  173. p('');
  174. p('Runtime log snooping (via DTrace, only on supported platforms):');
  175. p(' -p PID Process bunyan:log-* probes from the process');
  176. p(' with the given PID. Can be used multiple times,');
  177. p(' or specify all processes with "*", or a set of');
  178. p(' processes whose command & args match a pattern');
  179. p(' with "-p NAME".');
  180. p('');
  181. p('Filtering options:');
  182. p(' -l, --level LEVEL');
  183. p(' Only show messages at or above the specified level.');
  184. p(' You can specify level *names* or the internal numeric');
  185. p(' values.');
  186. p(' -c, --condition CONDITION');
  187. p(' Run each log message through the condition and');
  188. p(' only show those that return truish. E.g.:');
  189. p(' -c \'this.pid == 123\'');
  190. p(' -c \'this.level == DEBUG\'');
  191. p(' -c \'this.msg.indexOf("boom") != -1\'');
  192. p(' "CONDITION" must be legal JS code. `this` holds');
  193. p(' the log record. The TRACE, DEBUG, ... FATAL values');
  194. p(' are defined to help with comparing `this.level`.');
  195. p(' --strict Suppress all but legal Bunyan JSON log lines. By default');
  196. p(' non-JSON, and non-Bunyan lines are passed through.');
  197. p('');
  198. p('Output options:');
  199. p(' --pager Pipe output into `less` (or $PAGER if set), if');
  200. p(' stdout is a TTY. This overrides $BUNYAN_NO_PAGER.');
  201. p(' Note: Paging is only supported on node >=0.8.');
  202. p(' --no-pager Do not pipe output into a pager.');
  203. p(' --color Colorize output. Defaults to try if output');
  204. p(' stream is a TTY.');
  205. p(' --no-color Force no coloring (e.g. terminal doesn\'t support it)');
  206. p(' -o, --output MODE');
  207. p(' Specify an output mode/format. One of');
  208. p(' long: (the default) pretty');
  209. p(' json: JSON output, 2-space indent');
  210. p(' json-N: JSON output, N-space indent, e.g. "json-4"');
  211. p(' bunyan: 0 indented JSON, bunyan\'s native format');
  212. p(' inspect: node.js `util.inspect` output');
  213. p(' short: like "long", but more concise');
  214. p(' simple: level, followed by "-" and then the message');
  215. p(' -j shortcut for `-o json`');
  216. p(' -0 shortcut for `-o bunyan`');
  217. p(' -L, --time local');
  218. p(' Display time field in local time, rather than UTC.');
  219. p('');
  220. p('Environment Variables:');
  221. p(' BUNYAN_NO_COLOR Set to a non-empty value to force no output ');
  222. p(' coloring. See "--no-color".');
  223. p(' BUNYAN_NO_PAGER Disable piping output to a pager. ');
  224. p(' See "--no-pager".');
  225. p('');
  226. p('See <https://github.com/trentm/node-bunyan> for more complete docs.');
  227. p('Please report bugs to <https://github.com/trentm/node-bunyan/issues>.');
  228. /* END JSSTYLED */
  229. }
  230. /*
  231. * If the user specifies multiple input sources, we want to print out records
  232. * from all sources in a single, chronologically ordered stream. To do this
  233. * efficiently, we first assume that all records within each source are ordered
  234. * already, so we need only keep track of the next record in each source and
  235. * the time of the last record emitted. To avoid excess memory usage, we
  236. * pause() streams that are ahead of others.
  237. *
  238. * 'streams' is an object indexed by source name (file name) which specifies:
  239. *
  240. * stream Actual stream object, so that we can pause and resume it.
  241. *
  242. * records Array of log records we've read, but not yet emitted. Each
  243. * record includes 'line' (the raw line), 'rec' (the JSON
  244. * record), and 'time' (the parsed time value).
  245. *
  246. * done Whether the stream has any more records to emit.
  247. */
  248. var streams = {};
  249. function gotRecord(file, line, rec, opts, stylize)
  250. {
  251. var time = new Date(rec.time);
  252. streams[file]['records'].push({ line: line, rec: rec, time: time });
  253. emitNextRecord(opts, stylize);
  254. }
  255. function filterRecord(rec, opts)
  256. {
  257. if (opts.level && rec.level < opts.level) {
  258. return false;
  259. }
  260. if (opts.condFuncs) {
  261. var recCopy = objCopy(rec);
  262. for (var i = 0; i < opts.condFuncs.length; i++) {
  263. var pass = opts.condFuncs[i].call(recCopy);
  264. if (!pass)
  265. return false;
  266. }
  267. } else if (opts.condVm) {
  268. for (var i = 0; i < opts.condVm.length; i++) {
  269. var pass = opts.condVm[i].runInNewContext(rec);
  270. if (!pass)
  271. return false;
  272. }
  273. }
  274. return true;
  275. }
  276. function emitNextRecord(opts, stylize)
  277. {
  278. var ofile, ready, minfile, rec;
  279. for (;;) {
  280. /*
  281. * Take a first pass through the input streams to see if we have a
  282. * record from all of them. If not, we'll pause any streams for
  283. * which we do already have a record (to avoid consuming excess
  284. * memory) and then wait until we have records from the others
  285. * before emitting the next record.
  286. *
  287. * As part of the same pass, we look for the earliest record
  288. * we have not yet emitted.
  289. */
  290. minfile = undefined;
  291. ready = true;
  292. for (ofile in streams) {
  293. if (streams[ofile].stream === null ||
  294. (!streams[ofile].done && streams[ofile].records.length === 0)) {
  295. ready = false;
  296. break;
  297. }
  298. if (streams[ofile].records.length > 0 &&
  299. (minfile === undefined ||
  300. streams[minfile].records[0].time >
  301. streams[ofile].records[0].time)) {
  302. minfile = ofile;
  303. }
  304. }
  305. if (!ready || minfile === undefined) {
  306. for (ofile in streams) {
  307. if (!streams[ofile].stream || streams[ofile].done)
  308. continue;
  309. if (streams[ofile].records.length > 0) {
  310. if (!streams[ofile].paused) {
  311. streams[ofile].paused = true;
  312. streams[ofile].stream.pause();
  313. }
  314. } else if (streams[ofile].paused) {
  315. streams[ofile].paused = false;
  316. streams[ofile].stream.resume();
  317. }
  318. }
  319. return;
  320. }
  321. /*
  322. * Emit the next record for 'minfile', and invoke ourselves again to
  323. * make sure we emit as many records as we can right now.
  324. */
  325. rec = streams[minfile].records.shift();
  326. emitRecord(rec.rec, rec.line, opts, stylize);
  327. }
  328. }
  329. /**
  330. * Return a function for the given JS code that returns.
  331. *
  332. * If no 'return' in the given javascript snippet, then assume we are a single
  333. * statement and wrap in 'return (...)'. This is for convenience for short
  334. * '-c ...' snippets.
  335. */
  336. function funcWithReturnFromSnippet(js) {
  337. // auto-"return"
  338. if (js.indexOf('return') === -1) {
  339. if (js.substring(js.length - 1) === ';') {
  340. js = js.substring(0, js.length - 1);
  341. }
  342. js = 'return (' + js + ')';
  343. }
  344. // Expose level definitions to condition func context
  345. var varDefs = [];
  346. Object.keys(upperNameFromLevel).forEach(function (lvl) {
  347. varDefs.push(format('var %s = %d;',
  348. upperNameFromLevel[lvl], lvl));
  349. });
  350. varDefs = varDefs.join('\n') + '\n';
  351. return (new Function(varDefs + js));
  352. }
  353. /**
  354. * Parse the command-line options and arguments into an object.
  355. *
  356. * {
  357. * 'args': [...] // arguments
  358. * 'help': true, // true if '-h' option given
  359. * // etc.
  360. * }
  361. *
  362. * @return {Object} The parsed options. `.args` is the argument list.
  363. * @throws {Error} If there is an error parsing argv.
  364. */
  365. function parseArgv(argv) {
  366. var parsed = {
  367. args: [],
  368. help: false,
  369. color: null,
  370. paginate: null,
  371. outputMode: OM_LONG,
  372. jsonIndent: 2,
  373. level: null,
  374. strict: false,
  375. pids: null,
  376. pidsType: null,
  377. timeFormat: TIME_UTC // one of the TIME_ constants
  378. };
  379. // Turn '-iH' into '-i -H', except for argument-accepting options.
  380. var args = argv.slice(2); // drop ['node', 'scriptname']
  381. var newArgs = [];
  382. var optTakesArg = {'d': true, 'o': true, 'c': true, 'l': true, 'p': true};
  383. for (var i = 0; i < args.length; i++) {
  384. if (args[i].charAt(0) === '-' && args[i].charAt(1) !== '-' &&
  385. args[i].length > 2)
  386. {
  387. var splitOpts = args[i].slice(1).split('');
  388. for (var j = 0; j < splitOpts.length; j++) {
  389. newArgs.push('-' + splitOpts[j]);
  390. if (optTakesArg[splitOpts[j]]) {
  391. var optArg = splitOpts.slice(j+1).join('');
  392. if (optArg.length) {
  393. newArgs.push(optArg);
  394. }
  395. break;
  396. }
  397. }
  398. } else {
  399. newArgs.push(args[i]);
  400. }
  401. }
  402. args = newArgs;
  403. // Expose level definitions to condition vm context
  404. var condDefines = [];
  405. Object.keys(upperNameFromLevel).forEach(function (lvl) {
  406. condDefines.push(
  407. format('Object.prototype.%s = %s;', upperNameFromLevel[lvl], lvl));
  408. });
  409. condDefines = condDefines.join('\n') + '\n';
  410. var endOfOptions = false;
  411. while (args.length > 0) {
  412. var arg = args.shift();
  413. switch (arg) {
  414. case '--':
  415. endOfOptions = true;
  416. break;
  417. case '-h': // display help and exit
  418. case '--help':
  419. parsed.help = true;
  420. break;
  421. case '--version':
  422. parsed.version = true;
  423. break;
  424. case '--strict':
  425. parsed.strict = true;
  426. break;
  427. case '--color':
  428. parsed.color = true;
  429. break;
  430. case '--no-color':
  431. parsed.color = false;
  432. break;
  433. case '--pager':
  434. parsed.paginate = true;
  435. break;
  436. case '--no-pager':
  437. parsed.paginate = false;
  438. break;
  439. case '-o':
  440. case '--output':
  441. var name = args.shift();
  442. var idx = name.lastIndexOf('-');
  443. if (idx !== -1) {
  444. var indentation = Number(name.slice(idx+1));
  445. if (! isNaN(indentation)) {
  446. parsed.jsonIndent = indentation;
  447. name = name.slice(0, idx);
  448. }
  449. }
  450. parsed.outputMode = OM_FROM_NAME[name];
  451. if (parsed.outputMode === undefined) {
  452. throw new Error('unknown output mode: "'+name+'"');
  453. }
  454. break;
  455. case '-j': // output with JSON.stringify
  456. parsed.outputMode = OM_JSON;
  457. break;
  458. case '-0':
  459. parsed.outputMode = OM_BUNYAN;
  460. break;
  461. case '-L':
  462. parsed.timeFormat = TIME_LOCAL;
  463. if (!moment) {
  464. throw new Error(
  465. 'could not find moment package required for "-L"');
  466. }
  467. break;
  468. case '--time':
  469. var timeArg = args.shift();
  470. switch (timeArg) {
  471. case 'utc':
  472. parsed.timeFormat = TIME_UTC;
  473. break
  474. case 'local':
  475. parsed.timeFormat = TIME_LOCAL;
  476. if (!moment) {
  477. throw new Error('could not find moment package '
  478. + 'required for "--time=local"');
  479. }
  480. break
  481. case undefined:
  482. throw new Error('missing argument to "--time"');
  483. default:
  484. throw new Error(format('invalid time format: "%s"',
  485. timeArg));
  486. }
  487. break;
  488. case '-p':
  489. if (!parsed.pids) {
  490. parsed.pids = [];
  491. }
  492. var pidArg = args.shift();
  493. var pid = +(pidArg);
  494. if (!isNaN(pid) || pidArg === '*') {
  495. if (parsed.pidsType && parsed.pidsType !== 'num') {
  496. throw new Error(format('cannot mix PID name and '
  497. + 'number arguments: "%s"', pidArg));
  498. }
  499. parsed.pidsType = 'num';
  500. if (!parsed.pids) {
  501. parsed.pids = [];
  502. }
  503. parsed.pids.push(isNaN(pid) ? pidArg : pid);
  504. } else {
  505. if (parsed.pidsType && parsed.pidsType !== 'name') {
  506. throw new Error(format('cannot mix PID name and '
  507. + 'number arguments: "%s"', pidArg));
  508. }
  509. parsed.pidsType = 'name';
  510. parsed.pids = pidArg;
  511. }
  512. break;
  513. case '-l':
  514. case '--level':
  515. var levelArg = args.shift();
  516. var level = +(levelArg);
  517. if (isNaN(level)) {
  518. level = +levelFromName[levelArg.toLowerCase()];
  519. }
  520. if (isNaN(level)) {
  521. throw new Error('unknown level value: "'+levelArg+'"');
  522. }
  523. parsed.level = level;
  524. break;
  525. case '-c':
  526. case '--condition':
  527. gUsingConditionOpts = true;
  528. var condition = args.shift();
  529. if (Boolean(process.env.BUNYAN_EXEC &&
  530. process.env.BUNYAN_EXEC === 'vm'))
  531. {
  532. parsed.condVm = parsed.condVm || [];
  533. var scriptName = 'bunyan-condition-'+parsed.condVm.length;
  534. var code = condDefines + condition;
  535. var script;
  536. try {
  537. script = vm.createScript(code, scriptName);
  538. } catch (complErr) {
  539. throw new Error(format('illegal CONDITION code: %s\n'
  540. + ' CONDITION script:\n'
  541. + '%s\n'
  542. + ' Error:\n'
  543. + '%s',
  544. complErr, indent(code), indent(complErr.stack)));
  545. }
  546. // Ensure this is a reasonably safe CONDITION.
  547. try {
  548. script.runInNewContext(minValidRecord);
  549. } catch (condErr) {
  550. throw new Error(format(
  551. /* JSSTYLED */
  552. 'CONDITION code cannot safely filter a minimal Bunyan log record\n'
  553. + ' CONDITION script:\n'
  554. + '%s\n'
  555. + ' Minimal Bunyan log record:\n'
  556. + '%s\n'
  557. + ' Filter error:\n'
  558. + '%s',
  559. indent(code),
  560. indent(JSON.stringify(minValidRecord, null, 2)),
  561. indent(condErr.stack)
  562. ));
  563. }
  564. parsed.condVm.push(script);
  565. } else {
  566. parsed.condFuncs = parsed.condFuncs || [];
  567. parsed.condFuncs.push(funcWithReturnFromSnippet(condition));
  568. }
  569. break;
  570. default: // arguments
  571. if (!endOfOptions && arg.length > 0 && arg[0] === '-') {
  572. throw new Error('unknown option "'+arg+'"');
  573. }
  574. parsed.args.push(arg);
  575. break;
  576. }
  577. }
  578. //TODO: '--' handling and error on a first arg that looks like an option.
  579. return parsed;
  580. }
  581. function isInteger(s) {
  582. return (s.search(/^-?[0-9]+$/) == 0);
  583. }
  584. // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
  585. // Suggested colors (some are unreadable in common cases):
  586. // - Good: cyan, yellow (limited use), bold, green, magenta, red
  587. // - Bad: blue (not visible on cmd.exe), grey (same color as background on
  588. // Solarized Dark theme from <https://github.com/altercation/solarized>, see
  589. // issue #160)
  590. var colors = {
  591. 'bold' : [1, 22],
  592. 'italic' : [3, 23],
  593. 'underline' : [4, 24],
  594. 'inverse' : [7, 27],
  595. 'white' : [37, 39],
  596. 'grey' : [90, 39],
  597. 'black' : [30, 39],
  598. 'blue' : [34, 39],
  599. 'cyan' : [36, 39],
  600. 'green' : [32, 39],
  601. 'magenta' : [35, 39],
  602. 'red' : [31, 39],
  603. 'yellow' : [33, 39]
  604. };
  605. function stylizeWithColor(str, color) {
  606. if (!str)
  607. return '';
  608. var codes = colors[color];
  609. if (codes) {
  610. return '\033[' + codes[0] + 'm' + str +
  611. '\033[' + codes[1] + 'm';
  612. } else {
  613. return str;
  614. }
  615. }
  616. function stylizeWithoutColor(str, color) {
  617. return str;
  618. }
  619. /**
  620. * Is this a valid Bunyan log record.
  621. */
  622. function isValidRecord(rec) {
  623. if (rec.v == null ||
  624. rec.level == null ||
  625. rec.name == null ||
  626. rec.hostname == null ||
  627. rec.pid == null ||
  628. rec.time == null ||
  629. rec.msg == null) {
  630. // Not valid Bunyan log.
  631. return false;
  632. } else {
  633. return true;
  634. }
  635. }
  636. var minValidRecord = {
  637. v: 0, //TODO: get this from bunyan.LOG_VERSION
  638. level: INFO,
  639. name: 'name',
  640. hostname: 'hostname',
  641. pid: 123,
  642. time: Date.now(),
  643. msg: 'msg'
  644. };
  645. /**
  646. * Parses the given log line and either emits it right away (for invalid
  647. * records) or enqueues it for emitting later when it's the next line to show.
  648. */
  649. function handleLogLine(file, line, opts, stylize) {
  650. currLine = line; // intentionally global
  651. // Emit non-JSON lines immediately.
  652. var rec;
  653. if (!line) {
  654. if (!opts.strict) emit(line + '\n');
  655. return;
  656. } else if (line[0] !== '{') {
  657. if (!opts.strict) emit(line + '\n'); // not JSON
  658. return;
  659. } else {
  660. try {
  661. rec = JSON.parse(line);
  662. } catch (e) {
  663. if (!opts.strict) emit(line + '\n');
  664. return;
  665. }
  666. }
  667. if (!isValidRecord(rec)) {
  668. if (!opts.strict) emit(line + '\n');
  669. return;
  670. }
  671. if (!filterRecord(rec, opts))
  672. return;
  673. if (file === null)
  674. return emitRecord(rec, line, opts, stylize);
  675. return gotRecord(file, line, rec, opts, stylize);
  676. }
  677. /**
  678. * Print out a single result, considering input options.
  679. */
  680. function emitRecord(rec, line, opts, stylize) {
  681. var short = false;
  682. switch (opts.outputMode) {
  683. case OM_SHORT:
  684. short = true;
  685. /* jsl:fall-thru */
  686. case OM_LONG:
  687. // [time] LEVEL: name[/comp]/pid on hostname (src): msg* (extras...)
  688. // msg*
  689. // --
  690. // long and multi-line extras
  691. // ...
  692. // If 'msg' is single-line, then it goes in the top line.
  693. // If 'req', show the request.
  694. // If 'res', show the response.
  695. // If 'err' and 'err.stack' then show that.
  696. if (!isValidRecord(rec)) {
  697. return emit(line + '\n');
  698. }
  699. delete rec.v;
  700. // Time.
  701. var time;
  702. if (!short && opts.timeFormat === TIME_UTC) {
  703. // Fast default path: We assume the raw `rec.time` is a UTC time
  704. // in ISO 8601 format (per spec).
  705. time = '[' + rec.time + ']';
  706. } else if (!moment && opts.timeFormat === TIME_UTC) {
  707. // Don't require momentjs install, as long as not using TIME_LOCAL.
  708. time = rec.time.substr(11);
  709. } else {
  710. var tzFormat;
  711. var moTime = moment(rec.time);
  712. switch (opts.timeFormat) {
  713. case TIME_UTC:
  714. tzFormat = TIMEZONE_UTC_FORMATS[short ? 'short' : 'long'];
  715. moTime.utc();
  716. break;
  717. case TIME_LOCAL:
  718. tzFormat = TIMEZONE_LOCAL_FORMATS[short ? 'short' : 'long'];
  719. break;
  720. default:
  721. throw new Error('unexpected timeFormat: ' + opts.timeFormat);
  722. };
  723. time = moTime.format(tzFormat);
  724. }
  725. time = stylize(time, 'none');
  726. delete rec.time;
  727. var nameStr = rec.name;
  728. delete rec.name;
  729. if (rec.component) {
  730. nameStr += '/' + rec.component;
  731. }
  732. delete rec.component;
  733. if (!short)
  734. nameStr += '/' + rec.pid;
  735. delete rec.pid;
  736. var level = (upperPaddedNameFromLevel[rec.level] || 'LVL' + rec.level);
  737. if (opts.color) {
  738. var colorFromLevel = {
  739. 10: 'white', // TRACE
  740. 20: 'yellow', // DEBUG
  741. 30: 'cyan', // INFO
  742. 40: 'magenta', // WARN
  743. 50: 'red', // ERROR
  744. 60: 'inverse', // FATAL
  745. };
  746. level = stylize(level, colorFromLevel[rec.level]);
  747. }
  748. delete rec.level;
  749. var src = '';
  750. if (rec.src && rec.src.file) {
  751. var s = rec.src;
  752. if (s.func) {
  753. src = format(' (%s:%d in %s)', s.file, s.line, s.func);
  754. } else {
  755. src = format(' (%s:%d)', s.file, s.line);
  756. }
  757. src = stylize(src, 'green');
  758. }
  759. delete rec.src;
  760. var hostname = rec.hostname;
  761. delete rec.hostname;
  762. var extras = [];
  763. var details = [];
  764. if (rec.req_id) {
  765. extras.push('req_id=' + rec.req_id);
  766. }
  767. delete rec.req_id;
  768. var onelineMsg;
  769. if (rec.msg.indexOf('\n') !== -1) {
  770. onelineMsg = '';
  771. details.push(indent(stylize(rec.msg, 'cyan')));
  772. } else {
  773. onelineMsg = ' ' + stylize(rec.msg, 'cyan');
  774. }
  775. delete rec.msg;
  776. if (rec.req && typeof (rec.req) === 'object') {
  777. var req = rec.req;
  778. delete rec.req;
  779. var headers = req.headers;
  780. if (!headers) {
  781. headers = '';
  782. } else if (typeof (headers) === 'string') {
  783. headers = '\n' + headers;
  784. } else if (typeof (headers) === 'object') {
  785. headers = '\n' + Object.keys(headers).map(function (h) {
  786. return h + ': ' + headers[h];
  787. }).join('\n');
  788. }
  789. var s = format('%s %s HTTP/%s%s', req.method,
  790. req.url,
  791. req.httpVersion || '1.1',
  792. headers
  793. );
  794. delete req.url;
  795. delete req.method;
  796. delete req.httpVersion;
  797. delete req.headers;
  798. if (req.body) {
  799. s += '\n\n' + (typeof (req.body) === 'object'
  800. ? JSON.stringify(req.body, null, 2) : req.body);
  801. delete req.body;
  802. }
  803. if (req.trailers && Object.keys(req.trailers) > 0) {
  804. s += '\n' + Object.keys(req.trailers).map(function (t) {
  805. return t + ': ' + req.trailers[t];
  806. }).join('\n');
  807. }
  808. delete req.trailers;
  809. details.push(indent(s));
  810. // E.g. for extra 'foo' field on 'req', add 'req.foo' at
  811. // top-level. This *does* have the potential to stomp on a
  812. // literal 'req.foo' key.
  813. Object.keys(req).forEach(function (k) {
  814. rec['req.' + k] = req[k];
  815. })
  816. }
  817. if (rec.client_req && typeof (rec.client_req) === 'object') {
  818. var client_req = rec.client_req;
  819. delete rec.client_req;
  820. var headers = client_req.headers;
  821. var hostHeaderLine = '';
  822. var s = '';
  823. if (client_req.address) {
  824. hostHeaderLine = '\nHost: ' + client_req.address;
  825. if (client_req.port)
  826. hostHeaderLine += ':' + client_req.port;
  827. }
  828. delete client_req.headers;
  829. delete client_req.address;
  830. delete client_req.port;
  831. s += format('%s %s HTTP/%s%s%s', client_req.method,
  832. client_req.url,
  833. client_req.httpVersion || '1.1',
  834. hostHeaderLine,
  835. (headers ?
  836. '\n' + Object.keys(headers).map(
  837. function (h) {
  838. return h + ': ' + headers[h];
  839. }).join('\n') :
  840. ''));
  841. delete client_req.method;
  842. delete client_req.url;
  843. delete client_req.httpVersion;
  844. if (client_req.body) {
  845. s += '\n\n' + (typeof (client_req.body) === 'object' ?
  846. JSON.stringify(client_req.body, null, 2) :
  847. client_req.body);
  848. delete client_req.body;
  849. }
  850. // E.g. for extra 'foo' field on 'client_req', add
  851. // 'client_req.foo' at top-level. This *does* have the potential
  852. // to stomp on a literal 'client_req.foo' key.
  853. Object.keys(client_req).forEach(function (k) {
  854. rec['client_req.' + k] = client_req[k];
  855. })
  856. details.push(indent(s));
  857. }
  858. function _res(res) {
  859. var s = '';
  860. if (res.statusCode !== undefined) {
  861. s += format('HTTP/1.1 %s %s\n', res.statusCode,
  862. http.STATUS_CODES[res.statusCode]);
  863. delete res.statusCode;
  864. }
  865. // Handle `res.header` or `res.headers` as either a string or
  866. // and object of header key/value pairs. Prefer `res.header` if set
  867. // (TODO: Why? I don't recall. Typical of restify serializer?
  868. // Typical JSON.stringify of a core node HttpResponse?)
  869. var headerTypes = {string: true, object: true};
  870. var headers;
  871. if (res.header && headerTypes[typeof (res.header)]) {
  872. headers = res.header;
  873. delete res.header;
  874. } else if (res.headers && headerTypes[typeof (res.headers)]) {
  875. headers = res.headers;
  876. delete res.headers;
  877. }
  878. if (headers === undefined) {
  879. /* pass through */
  880. } else if (typeof (headers) === 'string') {
  881. s += headers.trimRight();
  882. } else {
  883. s += Object.keys(headers).map(
  884. function (h) { return h + ': ' + headers[h]; }).join('\n');
  885. }
  886. if (res.body !== undefined) {
  887. var body = (typeof (res.body) === 'object'
  888. ? JSON.stringify(res.body, null, 2) : res.body);
  889. if (body.length > 0) { s += '\n\n' + body };
  890. delete res.body;
  891. } else {
  892. s = s.trimRight();
  893. }
  894. if (res.trailer) {
  895. s += '\n' + res.trailer;
  896. }
  897. delete res.trailer;
  898. if (s) {
  899. details.push(indent(s));
  900. }
  901. // E.g. for extra 'foo' field on 'res', add 'res.foo' at
  902. // top-level. This *does* have the potential to stomp on a
  903. // literal 'res.foo' key.
  904. Object.keys(res).forEach(function (k) {
  905. rec['res.' + k] = res[k];
  906. });
  907. }
  908. if (rec.res && typeof (rec.res) === 'object') {
  909. _res(rec.res);
  910. delete rec.res;
  911. }
  912. if (rec.client_res && typeof (rec.client_res) === 'object') {
  913. _res(rec.client_res);
  914. delete rec.client_res;
  915. }
  916. if (rec.err && rec.err.stack) {
  917. var err = rec.err
  918. if (typeof (err.stack) !== 'string') {
  919. details.push(indent(err.stack.toString()));
  920. } else {
  921. details.push(indent(err.stack));
  922. }
  923. delete err.message;
  924. delete err.name;
  925. delete err.stack;
  926. // E.g. for extra 'foo' field on 'err', add 'err.foo' at
  927. // top-level. This *does* have the potential to stomp on a
  928. // literal 'err.foo' key.
  929. Object.keys(err).forEach(function (k) {
  930. rec['err.' + k] = err[k];
  931. })
  932. delete rec.err;
  933. }
  934. var leftover = Object.keys(rec);
  935. for (var i = 0; i < leftover.length; i++) {
  936. var key = leftover[i];
  937. var value = rec[key];
  938. var stringified = false;
  939. if (typeof (value) !== 'string') {
  940. value = JSON.stringify(value, null, 2);
  941. stringified = true;
  942. }
  943. if (value.indexOf('\n') !== -1 || value.length > 50) {
  944. details.push(indent(key + ': ' + value));
  945. } else if (!stringified && (value.indexOf(' ') != -1 ||
  946. value.length === 0))
  947. {
  948. extras.push(key + '=' + JSON.stringify(value));
  949. } else {
  950. extras.push(key + '=' + value);
  951. }
  952. }
  953. extras = stylize(
  954. (extras.length ? ' (' + extras.join(', ') + ')' : ''), 'none');
  955. details = stylize(
  956. (details.length ? details.join('\n --\n') + '\n' : ''), 'none');
  957. if (!short)
  958. emit(format('%s %s: %s on %s%s:%s%s\n%s',
  959. time,
  960. level,
  961. nameStr,
  962. hostname || '<no-hostname>',
  963. src,
  964. onelineMsg,
  965. extras,
  966. details));
  967. else
  968. emit(format('%s %s %s:%s%s\n%s',
  969. time,
  970. level,
  971. nameStr,
  972. onelineMsg,
  973. extras,
  974. details));
  975. break;
  976. case OM_INSPECT:
  977. emit(util.inspect(rec, false, Infinity, true) + '\n');
  978. break;
  979. case OM_BUNYAN:
  980. emit(JSON.stringify(rec, null, 0) + '\n');
  981. break;
  982. case OM_JSON:
  983. emit(JSON.stringify(rec, null, opts.jsonIndent) + '\n');
  984. break;
  985. case OM_SIMPLE:
  986. /* JSSTYLED */
  987. // <http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/SimpleLayout.html>
  988. if (!isValidRecord(rec)) {
  989. return emit(line + '\n');
  990. }
  991. emit(format('%s - %s\n',
  992. upperNameFromLevel[rec.level] || 'LVL' + rec.level,
  993. rec.msg));
  994. break;
  995. default:
  996. throw new Error('unknown output mode: '+opts.outputMode);
  997. }
  998. }
  999. var stdoutFlushed = true;
  1000. function emit(s) {
  1001. try {
  1002. stdoutFlushed = stdout.write(s);
  1003. } catch (e) {
  1004. // Handle any exceptions in stdout writing in `stdout.on('error', ...)`.
  1005. }
  1006. }
  1007. /**
  1008. * A hacked up version of 'process.exit' that will first drain stdout
  1009. * before exiting. *WARNING: This doesn't stop event processing.* IOW,
  1010. * callers have to be careful that code following this call isn't
  1011. * accidentally executed.
  1012. *
  1013. * In node v0.6 "process.stdout and process.stderr are blocking when they
  1014. * refer to regular files or TTY file descriptors." However, this hack might
  1015. * still be necessary in a shell pipeline.
  1016. */
  1017. function drainStdoutAndExit(code) {
  1018. if (_DEBUG) warn('(drainStdoutAndExit(%d))', code);
  1019. stdout.on('drain', function () {
  1020. cleanupAndExit(code);
  1021. });
  1022. if (stdoutFlushed) {
  1023. cleanupAndExit(code);
  1024. }
  1025. }
  1026. /**
  1027. * Process all input from stdin.
  1028. *
  1029. * @params opts {Object} Bunyan options object.
  1030. * @param stylize {Function} Output stylize function to use.
  1031. * @param callback {Function} `function ()`
  1032. */
  1033. function processStdin(opts, stylize, callback) {
  1034. var leftover = ''; // Left-over partial line from last chunk.
  1035. var stdin = process.stdin;
  1036. stdin.resume();
  1037. stdin.setEncoding('utf8');
  1038. stdin.on('data', function (chunk) {
  1039. var lines = chunk.split(/\r\n|\n/);
  1040. var length = lines.length;
  1041. if (length === 1) {
  1042. leftover += lines[0];
  1043. return;
  1044. }
  1045. if (length > 1) {
  1046. handleLogLine(null, leftover + lines[0], opts, stylize);
  1047. }
  1048. leftover = lines.pop();
  1049. length -= 1;
  1050. for (var i = 1; i < length; i++) {
  1051. handleLogLine(null, lines[i], opts, stylize);
  1052. }
  1053. });
  1054. stdin.on('end', function () {
  1055. if (leftover) {
  1056. handleLogLine(null, leftover, opts, stylize);
  1057. leftover = '';
  1058. }
  1059. callback();
  1060. });
  1061. }
  1062. /**
  1063. * Process bunyan:log-* probes from the given pid.
  1064. *
  1065. * @params opts {Object} Bunyan options object.
  1066. * @param stylize {Function} Output stylize function to use.
  1067. * @param callback {Function} `function (code)`
  1068. */
  1069. function processPids(opts, stylize, callback) {
  1070. var leftover = ''; // Left-over partial line from last chunk.
  1071. /**
  1072. * Get the PIDs to dtrace.
  1073. *
  1074. * @param cb {Function} `function (errCode, pids)`
  1075. */
  1076. function getPids(cb) {
  1077. if (opts.pidsType === 'num') {
  1078. return cb(null, opts.pids);
  1079. }
  1080. if (process.platform === 'sunos') {
  1081. execFile('/bin/pgrep', ['-lf', opts.pids],
  1082. function (pidsErr, stdout, stderr) {
  1083. if (pidsErr) {
  1084. warn('bunyan: error getting PIDs for "%s": %s\n%s\n%s',
  1085. opts.pids, pidsErr.message, stdout, stderr);
  1086. return cb(1);
  1087. }
  1088. var pids = stdout.trim().split('\n')
  1089. .map(function (line) {
  1090. return line.trim().split(/\s+/)[0]
  1091. })
  1092. .filter(function (pid) {
  1093. return Number(pid) !== process.pid
  1094. });
  1095. if (pids.length === 0) {
  1096. warn('bunyan: error: no matching PIDs found for "%s"',
  1097. opts.pids);
  1098. return cb(2);
  1099. }
  1100. cb(null, pids);
  1101. }
  1102. );
  1103. } else {
  1104. var regex = opts.pids;
  1105. if (regex && /[a-zA-Z0-9_]/.test(regex[0])) {
  1106. // 'foo' -> '[f]oo' trick to exclude the 'grep' PID from its
  1107. // own search.
  1108. regex = '[' + regex[0] + ']' + regex.slice(1);
  1109. }
  1110. exec(format('ps -A -o pid,command | grep \'%s\'', regex),
  1111. function (pidsErr, stdout, stderr) {
  1112. if (pidsErr) {
  1113. warn('bunyan: error getting PIDs for "%s": %s\n%s\n%s',
  1114. opts.pids, pidsErr.message, stdout, stderr);
  1115. return cb(1);
  1116. }
  1117. var pids = stdout.trim().split('\n')
  1118. .map(function (line) {
  1119. return line.trim().split(/\s+/)[0];
  1120. })
  1121. .filter(function (pid) {
  1122. return Number(pid) !== process.pid;
  1123. });
  1124. if (pids.length === 0) {
  1125. warn('bunyan: error: no matching PIDs found for "%s"',
  1126. opts.pids);
  1127. return cb(2);
  1128. }
  1129. cb(null, pids);
  1130. }
  1131. );
  1132. }
  1133. }
  1134. getPids(function (errCode, pids) {
  1135. if (errCode) {
  1136. return callback(errCode);
  1137. }
  1138. var probes = pids.map(function (pid) {
  1139. if (!opts.level)
  1140. return format('bunyan%s:::log-*', pid);
  1141. var rval = [], l;
  1142. for (l in levelFromName) {
  1143. if (levelFromName[l] >= opts.level)
  1144. rval.push(format('bunyan%s:::log-%s', pid, l));
  1145. }
  1146. if (rval.length != 0)
  1147. return rval.join(',');
  1148. warn('bunyan: error: level (%d) exceeds maximum logging level',
  1149. opts.level);
  1150. return drainStdoutAndExit(1);
  1151. }).join(',');
  1152. var argv = ['dtrace', '-Z', '-x', 'strsize=4k',
  1153. '-x', 'switchrate=10hz', '-qn',
  1154. format('%s{printf("%s", copyinstr(arg0))}', probes)];
  1155. //console.log('dtrace argv: %s', argv);
  1156. var dtrace = spawn(argv[0], argv.slice(1),
  1157. // Share the stderr handle to have error output come
  1158. // straight through. Only supported in v0.8+.
  1159. {stdio: ['pipe', 'pipe', process.stderr]});
  1160. dtrace.on('error', function (e) {
  1161. if (e.syscall === 'spawn' && e.errno === 'ENOENT') {
  1162. console.error('bunyan: error: could not spawn "dtrace" ' +
  1163. '("bunyan -p" is only supported on platforms with dtrace)');
  1164. } else {
  1165. console.error('bunyan: error: unexpected dtrace error: %s', e);
  1166. }
  1167. callback(1);
  1168. })
  1169. child = dtrace; // intentionally global
  1170. function finish(code) {
  1171. if (leftover) {
  1172. handleLogLine(null, leftover, opts, stylize);
  1173. leftover = '';
  1174. }
  1175. callback(code);
  1176. }
  1177. dtrace.stdout.setEncoding('utf8');
  1178. dtrace.stdout.on('data', function (chunk) {
  1179. var lines = chunk.split(/\r\n|\n/);
  1180. var length = lines.length;
  1181. if (length === 1) {
  1182. leftover += lines[0];
  1183. return;
  1184. }
  1185. if (length > 1) {
  1186. handleLogLine(null, leftover + lines[0], opts, stylize);
  1187. }
  1188. leftover = lines.pop();
  1189. length -= 1;
  1190. for (var i = 1; i < length; i++) {
  1191. handleLogLine(null, lines[i], opts, stylize);
  1192. }
  1193. });
  1194. if (nodeSpawnSupportsStdio) {
  1195. dtrace.on('exit', finish);
  1196. } else {
  1197. // Fallback (for < v0.8) to pipe the dtrace process' stderr to
  1198. // this stderr. Wait for all of (1) process 'exit', (2) stderr
  1199. // 'end', and (2) stdout 'end' before returning to ensure all
  1200. // stderr is flushed (issue #54).
  1201. var returnCode = null;
  1202. var eventsRemaining = 3;
  1203. function countdownToFinish(code) {
  1204. returnCode = code;
  1205. eventsRemaining--;
  1206. if (eventsRemaining == 0) {
  1207. finish(returnCode);
  1208. }
  1209. }
  1210. dtrace.stderr.pipe(process.stderr);
  1211. dtrace.stderr.on('end', countdownToFinish);
  1212. dtrace.stderr.on('end', countdownToFinish);
  1213. dtrace.on('exit', countdownToFinish);
  1214. }
  1215. });
  1216. }
  1217. /**
  1218. * Process all input from the given log file.
  1219. *
  1220. * @param file {String} Log file path to process.
  1221. * @params opts {Object} Bunyan options object.
  1222. * @param stylize {Function} Output stylize function to use.
  1223. * @param callback {Function} `function ()`
  1224. */
  1225. function processFile(file, opts, stylize, callback) {
  1226. var stream = fs.createReadStream(file);
  1227. if (/\.gz$/.test(file)) {
  1228. stream = stream.pipe(require('zlib').createGunzip());
  1229. }
  1230. // Manually decode streams - lazy load here as per node/lib/fs.js
  1231. var decoder = new (require('string_decoder').StringDecoder)('utf8');
  1232. streams[file].stream = stream;
  1233. stream.on('error', function (err) {
  1234. streams[file].done = true;
  1235. callback(err);
  1236. });
  1237. var leftover = ''; // Left-over partial line from last chunk.
  1238. stream.on('data', function (data) {
  1239. var chunk = decoder.write(data);
  1240. if (!chunk.length) {
  1241. return;
  1242. }
  1243. var lines = chunk.split(/\r\n|\n/);
  1244. var length = lines.length;
  1245. if (length === 1) {
  1246. leftover += lines[0];
  1247. return;
  1248. }
  1249. if (length > 1) {
  1250. handleLogLine(file, leftover + lines[0], opts, stylize);
  1251. }
  1252. leftover = lines.pop();
  1253. length -= 1;
  1254. for (var i = 1; i < length; i++) {
  1255. handleLogLine(file, lines[i], opts, stylize);
  1256. }
  1257. });
  1258. stream.on('end', function () {
  1259. streams[file].done = true;
  1260. if (leftover) {
  1261. handleLogLine(file, leftover, opts, stylize);
  1262. leftover = '';
  1263. } else {
  1264. emitNextRecord(opts, stylize);
  1265. }
  1266. callback();
  1267. });
  1268. }
  1269. /**
  1270. * From node async module.
  1271. */
  1272. /* BEGIN JSSTYLED */
  1273. function asyncForEach(arr, iterator, callback) {
  1274. callback = callback || function () {};
  1275. if (!arr.length) {
  1276. return callback();
  1277. }
  1278. var completed = 0;
  1279. arr.forEach(function (x) {
  1280. iterator(x, function (err) {
  1281. if (err) {
  1282. callback(err);
  1283. callback = function () {};
  1284. }
  1285. else {
  1286. completed += 1;
  1287. if (completed === arr.length) {
  1288. callback();
  1289. }
  1290. }
  1291. });
  1292. });
  1293. };
  1294. /* END JSSTYLED */
  1295. /**
  1296. * Cleanup and exit properly.
  1297. *
  1298. * Warning: this doesn't stop processing, i.e. process exit might be delayed.
  1299. * It is up to the caller to ensure that no subsequent bunyan processing
  1300. * is done after calling this.
  1301. *
  1302. * @param code {Number} exit code.
  1303. * @param signal {String} Optional signal name, if this was exitting because
  1304. * of a signal.
  1305. */
  1306. var cleanedUp = false;
  1307. function cleanupAndExit(code, signal) {
  1308. // Guard one call.
  1309. if (cleanedUp) {
  1310. return;
  1311. }
  1312. cleanedUp = true;
  1313. if (_DEBUG) warn('(bunyan: cleanupAndExit)');
  1314. // Clear possibly interrupted ANSI code (issue #59).
  1315. if (usingAnsiCodes) {
  1316. stdout.write('\033[0m');
  1317. }
  1318. // Kill possible dtrace child.
  1319. if (child) {
  1320. child.kill(signal);
  1321. }
  1322. if (pager) {
  1323. // Let pager know that output is done, then wait for pager to exit.
  1324. stdout.end();
  1325. pager.on('exit', function (pagerCode) {
  1326. if (_DEBUG)
  1327. warn('(bunyan: pager exit -> process.exit(%s))',
  1328. pagerCode || code);
  1329. process.exit(pagerCode || code);
  1330. });
  1331. } else {
  1332. if (_DEBUG) warn('(bunyan: process.exit(%s))', code);
  1333. process.exit(code);
  1334. }
  1335. }
  1336. //---- mainline
  1337. process.on('SIGINT', function () { cleanupAndExit(1, 'SIGINT'); });
  1338. process.on('SIGQUIT', function () { cleanupAndExit(1, 'SIGQUIT'); });
  1339. process.on('SIGTERM', function () { cleanupAndExit(1, 'SIGTERM'); });
  1340. process.on('SIGHUP', function () { cleanupAndExit(1, 'SIGHUP'); });
  1341. process.on('uncaughtException', function (err) {
  1342. function _indent(s) {
  1343. var lines = s.split(/\r?\n/);
  1344. for (var i = 0; i < lines.length; i++) {
  1345. lines[i] = '* ' + lines[i];
  1346. }
  1347. return lines.join('\n');
  1348. }
  1349. var title = encodeURIComponent(format(
  1350. 'Bunyan %s crashed: %s', getVersion(), String(err)));
  1351. var e = console.error;
  1352. e('```');
  1353. e('* The Bunyan CLI crashed!');
  1354. e('*');
  1355. if (err.name === 'ReferenceError' && gUsingConditionOpts) {
  1356. /* BEGIN JSSTYLED */
  1357. e('* This crash was due to a "ReferenceError", which is often the result of given');
  1358. e('* `-c CONDITION` code that doesn\'t guard against undefined values. If that is');
  1359. /* END JSSTYLED */
  1360. e('* not the problem:');
  1361. e('*');
  1362. }
  1363. e('* Please report this issue and include the details below:');
  1364. e('*');
  1365. e('* https://github.com/trentm/node-bunyan/issues/new?title=%s', title);
  1366. e('*');
  1367. e('* * *');
  1368. e('* platform:', process.platform);
  1369. e('* node version:', process.version);
  1370. e('* bunyan version:', getVersion());
  1371. e('* argv: %j', process.argv);
  1372. e('* log line: %j', currLine);
  1373. e('* stack:');
  1374. e(_indent(err.stack));
  1375. e('```');
  1376. process.exit(1);
  1377. });
  1378. function main(argv) {
  1379. try {
  1380. var opts = parseArgv(argv);
  1381. } catch (e) {
  1382. warn('bunyan: error: %s', e.message);
  1383. return drainStdoutAndExit(1);
  1384. }
  1385. if (opts.help) {
  1386. printHelp();
  1387. return;
  1388. }
  1389. if (opts.version) {
  1390. console.log('bunyan ' + getVersion());
  1391. return;
  1392. }
  1393. if (opts.pids && opts.args.length > 0) {
  1394. warn('bunyan: error: can\'t use both "-p PID" (%s) and file (%s) args',
  1395. opts.pids, opts.args.join(' '));
  1396. return drainStdoutAndExit(1);
  1397. }
  1398. if (opts.color === null) {
  1399. if (process.env.BUNYAN_NO_COLOR &&
  1400. process.env.BUNYAN_NO_COLOR.length > 0) {
  1401. opts.color = false;
  1402. } else {
  1403. opts.color = process.stdout.isTTY;
  1404. }
  1405. }
  1406. usingAnsiCodes = opts.color; // intentionally global
  1407. var stylize = (opts.color ? stylizeWithColor : stylizeWithoutColor);
  1408. // Pager.
  1409. var paginate = (
  1410. process.stdout.isTTY &&
  1411. process.stdin.isTTY &&
  1412. !opts.pids && // Don't page if following process output.
  1413. opts.args.length > 0 && // Don't page if no file args to process.
  1414. process.platform !== 'win32' &&
  1415. (nodeVer[0] > 0 || nodeVer[1] >= 8) &&
  1416. (opts.paginate === true ||
  1417. (opts.paginate !== false &&
  1418. (!process.env.BUNYAN_NO_PAGER ||
  1419. process.env.BUNYAN_NO_PAGER.length === 0))));
  1420. if (paginate) {
  1421. var pagerCmd = process.env.PAGER || 'less';
  1422. /* JSSTYLED */
  1423. assert.ok(pagerCmd.indexOf('"') === -1 && pagerCmd.indexOf("'") === -1,
  1424. 'cannot parse PAGER quotes yet');
  1425. var argv = pagerCmd.split(/\s+/g);
  1426. var env = objCopy(process.env);
  1427. if (env.LESS === undefined) {
  1428. // git's default is LESS=FRSX. I don't like the 'S' here because
  1429. // lines are *typically* wide with bunyan output and scrolling
  1430. // horizontally is a royal pain. Note a bug in Mac's `less -F`,
  1431. // such that SIGWINCH can kill it. If that rears too much then
  1432. // I'll remove 'F' from here.
  1433. env.LESS = 'FRX';
  1434. }
  1435. if (_DEBUG) warn('(pager: argv=%j, env.LESS=%j)', argv, env.LESS);
  1436. // `pager` and `stdout` intentionally global.
  1437. pager = spawn(argv[0], argv.slice(1),
  1438. // Share the stderr handle to have error output come
  1439. // straight through. Only supported in v0.8+.
  1440. {env: env, stdio: ['pipe', 1, 2]});
  1441. stdout = pager.stdin;
  1442. // Early termination of the pager: just stop.
  1443. pager.on('exit', function (pagerCode) {
  1444. if (_DEBUG) warn('(bunyan: pager exit)');
  1445. pager = null;
  1446. stdout.end()
  1447. stdout = process.stdout;
  1448. cleanupAndExit(pagerCode);
  1449. });
  1450. }
  1451. // Stdout error handling. (Couldn't setup until `stdout` was determined.)
  1452. stdout.on('error', function (err) {
  1453. if (_DEBUG) warn('(stdout error event: %s)', err);
  1454. if (err.code === 'EPIPE') {
  1455. drainStdoutAndExit(0);
  1456. } else if (err.toString() === 'Error: This socket is closed.') {
  1457. // Could get this if the pager closes its stdin, but hasn't
  1458. // exited yet.
  1459. drainStdoutAndExit(1);
  1460. } else {
  1461. warn(err);
  1462. drainStdoutAndExit(1);
  1463. }
  1464. });
  1465. var retval = 0;
  1466. if (opts.pids) {
  1467. processPids(opts, stylize, function (code) {
  1468. cleanupAndExit(code);
  1469. });
  1470. } else if (opts.args.length > 0) {
  1471. var files = opts.args;
  1472. files.forEach(function (file) {
  1473. streams[file] = { stream: null, records: [], done: false }
  1474. });
  1475. asyncForEach(files,
  1476. function (file, next) {
  1477. processFile(file, opts, stylize, function (err) {
  1478. if (err) {
  1479. warn('bunyan: %s', err.message);
  1480. retval += 1;
  1481. }
  1482. next();
  1483. });
  1484. },
  1485. function (err) {
  1486. if (err) {
  1487. warn('bunyan: unexpected error: %s', err.stack || err);
  1488. return drainStdoutAndExit(1);
  1489. }
  1490. cleanupAndExit(retval);
  1491. }
  1492. );
  1493. } else {
  1494. processStdin(opts, stylize, function () {
  1495. cleanupAndExit(retval);
  1496. });
  1497. }
  1498. }
  1499. if (require.main === module) {
  1500. // HACK guard for <https://github.com/trentm/json/issues/24>.
  1501. // We override the `process.stdout.end` guard that core node.js puts in
  1502. // place. The real fix is that `.end()` shouldn't be called on stdout
  1503. // in node core. Node v0.6.9 fixes that. Only guard for v0.6.0..v0.6.8.
  1504. if ([0, 6, 0] <= nodeVer && nodeVer <= [0, 6, 8]) {
  1505. var stdout = process.stdout;
  1506. stdout.end = stdout.destroy = stdout.destroySoon = function () {
  1507. /* pass */
  1508. };
  1509. }
  1510. main(process.argv);
  1511. }