Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

bunyan 55KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708
  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.12';
  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. /*
  818. * `client_req` is the typical field name for an *HTTP client request
  819. * object* serialized by the restify-clients library. Render the
  820. * client request somewhat like `curl` debug output shows it.
  821. */
  822. if (rec.client_req && typeof (rec.client_req) === 'object') {
  823. var client_req = rec.client_req;
  824. delete rec.client_req;
  825. var headers = client_req.headers;
  826. delete client_req.headers;
  827. /*
  828. * `client_req.address`, and `client_req.port`, provide values for
  829. * a *likely* "Host" header that wasn't included in the serialized
  830. * headers. Node.js will often add this "Host" header in its
  831. * `http.ClientRequest`, e.g. for node v6.10.3:
  832. * // JSSTYLED
  833. * https://github.com/nodejs/node/blob/v6.10.3/lib/_http_client.js#L88-L105
  834. *
  835. * If `client_req.port` exists and is 80 or 443, we *assume* that
  836. * is the default protocol port, and elide it per the `defaultPort`
  837. * handling in the node.js link above.
  838. *
  839. * Note: This added Host header is a *guess*. Bunyan shouldn't be
  840. * doing this "favour" for users because it can be wrong and
  841. * misleading. Bunyan 2.x will drop adding this. See issue #504
  842. * for details.
  843. */
  844. var hostHeaderLine = '';
  845. if (!headers || !(
  846. Object.hasOwnProperty.call(headers, 'host') ||
  847. Object.hasOwnProperty.call(headers, 'Host') ||
  848. Object.hasOwnProperty.call(headers, 'HOST')
  849. )
  850. ) {
  851. if (Object.hasOwnProperty.call(client_req, 'address')) {
  852. hostHeaderLine = '\nHost: ' + client_req.address;
  853. if (Object.hasOwnProperty.call(client_req, 'port')) {
  854. // XXX
  855. var port = +client_req.port;
  856. if (port !== 80 && port !== 443) {
  857. hostHeaderLine += ':' + client_req.port;
  858. }
  859. delete client_req.port;
  860. }
  861. delete client_req.address;
  862. }
  863. }
  864. var s = format('%s %s HTTP/%s%s%s', client_req.method,
  865. client_req.url,
  866. client_req.httpVersion || '1.1',
  867. hostHeaderLine,
  868. (headers ?
  869. '\n' + Object.keys(headers).map(
  870. function (h) {
  871. return h + ': ' + headers[h];
  872. }).join('\n') :
  873. ''));
  874. delete client_req.method;
  875. delete client_req.url;
  876. delete client_req.httpVersion;
  877. if (client_req.body) {
  878. s += '\n\n' + (typeof (client_req.body) === 'object' ?
  879. JSON.stringify(client_req.body, null, 2) :
  880. client_req.body);
  881. delete client_req.body;
  882. }
  883. // E.g. for extra 'foo' field on 'client_req', add
  884. // 'client_req.foo' at top-level. This *does* have the potential
  885. // to stomp on a literal 'client_req.foo' key.
  886. Object.keys(client_req).forEach(function (k) {
  887. rec['client_req.' + k] = client_req[k];
  888. })
  889. details.push(indent(s));
  890. }
  891. function _res(res) {
  892. var s = '';
  893. /*
  894. * Handle `res.header` or `res.headers` as either a string or
  895. * an object of header key/value pairs. Prefer `res.header` if set,
  896. * because that's what Bunyan's own `res` serializer specifies,
  897. * because that's the value in Node.js's core HTTP server response
  898. * implementation that has all the implicit headers.
  899. *
  900. * Note: `res.header` (string) typically includes the 'HTTP/1.1 ...'
  901. * status line.
  902. */
  903. var headerTypes = {string: true, object: true};
  904. var headers;
  905. var headersStr = '';
  906. var headersHaveStatusLine = false;
  907. if (res.header && headerTypes[typeof (res.header)]) {
  908. headers = res.header;
  909. delete res.header;
  910. } else if (res.headers && headerTypes[typeof (res.headers)]) {
  911. headers = res.headers;
  912. delete res.headers;
  913. }
  914. if (headers === undefined) {
  915. /* pass through */
  916. } else if (typeof (headers) === 'string') {
  917. headersStr = headers.trimRight(); // Trim the CRLF.
  918. if (headersStr.slice(0, 5) === 'HTTP/') {
  919. headersHaveStatusLine = true;
  920. }
  921. } else {
  922. headersStr += Object.keys(headers).map(
  923. function (h) { return h + ': ' + headers[h]; }).join('\n');
  924. }
  925. /*
  926. * Add a 'HTTP/1.1 ...' status line if the headers didn't already
  927. * include it.
  928. */
  929. if (!headersHaveStatusLine && res.statusCode !== undefined) {
  930. s += format('HTTP/1.1 %s %s\n', res.statusCode,
  931. http.STATUS_CODES[res.statusCode]);
  932. }
  933. delete res.statusCode;
  934. s += headersStr;
  935. if (res.body !== undefined) {
  936. var body = (typeof (res.body) === 'object'
  937. ? JSON.stringify(res.body, null, 2) : res.body);
  938. if (body.length > 0) { s += '\n\n' + body };
  939. delete res.body;
  940. } else {
  941. s = s.trimRight();
  942. }
  943. if (res.trailer) {
  944. s += '\n' + res.trailer;
  945. }
  946. delete res.trailer;
  947. if (s) {
  948. details.push(indent(s));
  949. }
  950. // E.g. for extra 'foo' field on 'res', add 'res.foo' at
  951. // top-level. This *does* have the potential to stomp on a
  952. // literal 'res.foo' key.
  953. Object.keys(res).forEach(function (k) {
  954. rec['res.' + k] = res[k];
  955. });
  956. }
  957. if (rec.res && typeof (rec.res) === 'object') {
  958. _res(rec.res);
  959. delete rec.res;
  960. }
  961. if (rec.client_res && typeof (rec.client_res) === 'object') {
  962. _res(rec.client_res);
  963. delete rec.client_res;
  964. }
  965. if (rec.err && rec.err.stack) {
  966. var err = rec.err
  967. if (typeof (err.stack) !== 'string') {
  968. details.push(indent(err.stack.toString()));
  969. } else {
  970. details.push(indent(err.stack));
  971. }
  972. delete err.message;
  973. delete err.name;
  974. delete err.stack;
  975. // E.g. for extra 'foo' field on 'err', add 'err.foo' at
  976. // top-level. This *does* have the potential to stomp on a
  977. // literal 'err.foo' key.
  978. Object.keys(err).forEach(function (k) {
  979. rec['err.' + k] = err[k];
  980. })
  981. delete rec.err;
  982. }
  983. var leftover = Object.keys(rec);
  984. for (var i = 0; i < leftover.length; i++) {
  985. var key = leftover[i];
  986. var value = rec[key];
  987. var stringified = false;
  988. if (typeof (value) !== 'string') {
  989. value = JSON.stringify(value, null, 2);
  990. stringified = true;
  991. }
  992. if (value.indexOf('\n') !== -1 || value.length > 50) {
  993. details.push(indent(key + ': ' + value));
  994. } else if (!stringified && (value.indexOf(' ') != -1 ||
  995. value.length === 0))
  996. {
  997. extras.push(key + '=' + JSON.stringify(value));
  998. } else {
  999. extras.push(key + '=' + value);
  1000. }
  1001. }
  1002. extras = stylize(
  1003. (extras.length ? ' (' + extras.join(', ') + ')' : ''), 'none');
  1004. details = stylize(
  1005. (details.length ? details.join('\n --\n') + '\n' : ''), 'none');
  1006. if (!short)
  1007. emit(format('%s %s: %s on %s%s:%s%s\n%s',
  1008. time,
  1009. level,
  1010. nameStr,
  1011. hostname || '<no-hostname>',
  1012. src,
  1013. onelineMsg,
  1014. extras,
  1015. details));
  1016. else
  1017. emit(format('%s %s %s:%s%s\n%s',
  1018. time,
  1019. level,
  1020. nameStr,
  1021. onelineMsg,
  1022. extras,
  1023. details));
  1024. break;
  1025. case OM_INSPECT:
  1026. emit(util.inspect(rec, false, Infinity, true) + '\n');
  1027. break;
  1028. case OM_BUNYAN:
  1029. emit(JSON.stringify(rec, null, 0) + '\n');
  1030. break;
  1031. case OM_JSON:
  1032. emit(JSON.stringify(rec, null, opts.jsonIndent) + '\n');
  1033. break;
  1034. case OM_SIMPLE:
  1035. /* JSSTYLED */
  1036. // <http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/SimpleLayout.html>
  1037. if (!isValidRecord(rec)) {
  1038. return emit(line + '\n');
  1039. }
  1040. emit(format('%s - %s\n',
  1041. upperNameFromLevel[rec.level] || 'LVL' + rec.level,
  1042. rec.msg));
  1043. break;
  1044. default:
  1045. throw new Error('unknown output mode: '+opts.outputMode);
  1046. }
  1047. }
  1048. var stdoutFlushed = true;
  1049. function emit(s) {
  1050. try {
  1051. stdoutFlushed = stdout.write(s);
  1052. } catch (e) {
  1053. // Handle any exceptions in stdout writing in `stdout.on('error', ...)`.
  1054. }
  1055. }
  1056. /**
  1057. * A hacked up version of 'process.exit' that will first drain stdout
  1058. * before exiting. *WARNING: This doesn't stop event processing.* IOW,
  1059. * callers have to be careful that code following this call isn't
  1060. * accidentally executed.
  1061. *
  1062. * In node v0.6 "process.stdout and process.stderr are blocking when they
  1063. * refer to regular files or TTY file descriptors." However, this hack might
  1064. * still be necessary in a shell pipeline.
  1065. */
  1066. function drainStdoutAndExit(code) {
  1067. if (_DEBUG) warn('(drainStdoutAndExit(%d))', code);
  1068. stdout.on('drain', function () {
  1069. cleanupAndExit(code);
  1070. });
  1071. if (stdoutFlushed) {
  1072. cleanupAndExit(code);
  1073. }
  1074. }
  1075. /**
  1076. * Process all input from stdin.
  1077. *
  1078. * @params opts {Object} Bunyan options object.
  1079. * @param stylize {Function} Output stylize function to use.
  1080. * @param callback {Function} `function ()`
  1081. */
  1082. function processStdin(opts, stylize, callback) {
  1083. var leftover = ''; // Left-over partial line from last chunk.
  1084. var stdin = process.stdin;
  1085. stdin.resume();
  1086. stdin.setEncoding('utf8');
  1087. stdin.on('data', function (chunk) {
  1088. var lines = chunk.split(/\r\n|\n/);
  1089. var length = lines.length;
  1090. if (length === 1) {
  1091. leftover += lines[0];
  1092. return;
  1093. }
  1094. if (length > 1) {
  1095. handleLogLine(null, leftover + lines[0], opts, stylize);
  1096. }
  1097. leftover = lines.pop();
  1098. length -= 1;
  1099. for (var i = 1; i < length; i++) {
  1100. handleLogLine(null, lines[i], opts, stylize);
  1101. }
  1102. });
  1103. stdin.on('end', function () {
  1104. if (leftover) {
  1105. handleLogLine(null, leftover, opts, stylize);
  1106. leftover = '';
  1107. }
  1108. callback();
  1109. });
  1110. }
  1111. /**
  1112. * Process bunyan:log-* probes from the given pid.
  1113. *
  1114. * @params opts {Object} Bunyan options object.
  1115. * @param stylize {Function} Output stylize function to use.
  1116. * @param callback {Function} `function (code)`
  1117. */
  1118. function processPids(opts, stylize, callback) {
  1119. var leftover = ''; // Left-over partial line from last chunk.
  1120. /**
  1121. * Get the PIDs to dtrace.
  1122. *
  1123. * @param cb {Function} `function (errCode, pids)`
  1124. */
  1125. function getPids(cb) {
  1126. if (opts.pidsType === 'num') {
  1127. return cb(null, opts.pids);
  1128. }
  1129. if (process.platform === 'sunos') {
  1130. execFile('/bin/pgrep', ['-lf', opts.pids],
  1131. function (pidsErr, stdout, stderr) {
  1132. if (pidsErr) {
  1133. warn('bunyan: error getting PIDs for "%s": %s\n%s\n%s',
  1134. opts.pids, pidsErr.message, stdout, stderr);
  1135. return cb(1);
  1136. }
  1137. var pids = stdout.trim().split('\n')
  1138. .map(function (line) {
  1139. return line.trim().split(/\s+/)[0]
  1140. })
  1141. .filter(function (pid) {
  1142. return Number(pid) !== process.pid
  1143. });
  1144. if (pids.length === 0) {
  1145. warn('bunyan: error: no matching PIDs found for "%s"',
  1146. opts.pids);
  1147. return cb(2);
  1148. }
  1149. cb(null, pids);
  1150. }
  1151. );
  1152. } else {
  1153. var regex = opts.pids;
  1154. if (regex && /[a-zA-Z0-9_]/.test(regex[0])) {
  1155. // 'foo' -> '[f]oo' trick to exclude the 'grep' PID from its
  1156. // own search.
  1157. regex = '[' + regex[0] + ']' + regex.slice(1);
  1158. }
  1159. exec(format('ps -A -o pid,command | grep \'%s\'', regex),
  1160. function (pidsErr, stdout, stderr) {
  1161. if (pidsErr) {
  1162. warn('bunyan: error getting PIDs for "%s": %s\n%s\n%s',
  1163. opts.pids, pidsErr.message, stdout, stderr);
  1164. return cb(1);
  1165. }
  1166. var pids = stdout.trim().split('\n')
  1167. .map(function (line) {
  1168. return line.trim().split(/\s+/)[0];
  1169. })
  1170. .filter(function (pid) {
  1171. return Number(pid) !== process.pid;
  1172. });
  1173. if (pids.length === 0) {
  1174. warn('bunyan: error: no matching PIDs found for "%s"',
  1175. opts.pids);
  1176. return cb(2);
  1177. }
  1178. cb(null, pids);
  1179. }
  1180. );
  1181. }
  1182. }
  1183. getPids(function (errCode, pids) {
  1184. if (errCode) {
  1185. return callback(errCode);
  1186. }
  1187. var probes = pids.map(function (pid) {
  1188. if (!opts.level)
  1189. return format('bunyan%s:::log-*', pid);
  1190. var rval = [], l;
  1191. for (l in levelFromName) {
  1192. if (levelFromName[l] >= opts.level)
  1193. rval.push(format('bunyan%s:::log-%s', pid, l));
  1194. }
  1195. if (rval.length != 0)
  1196. return rval.join(',');
  1197. warn('bunyan: error: level (%d) exceeds maximum logging level',
  1198. opts.level);
  1199. return drainStdoutAndExit(1);
  1200. }).join(',');
  1201. var argv = ['dtrace', '-Z', '-x', 'strsize=4k',
  1202. '-x', 'switchrate=10hz', '-qn',
  1203. format('%s{printf("%s", copyinstr(arg0))}', probes)];
  1204. //console.log('dtrace argv: %s', argv);
  1205. var dtrace = spawn(argv[0], argv.slice(1),
  1206. // Share the stderr handle to have error output come
  1207. // straight through. Only supported in v0.8+.
  1208. {stdio: ['pipe', 'pipe', process.stderr]});
  1209. dtrace.on('error', function (e) {
  1210. if (e.syscall === 'spawn' && e.errno === 'ENOENT') {
  1211. console.error('bunyan: error: could not spawn "dtrace" ' +
  1212. '("bunyan -p" is only supported on platforms with dtrace)');
  1213. } else {
  1214. console.error('bunyan: error: unexpected dtrace error: %s', e);
  1215. }
  1216. callback(1);
  1217. })
  1218. child = dtrace; // intentionally global
  1219. function finish(code) {
  1220. if (leftover) {
  1221. handleLogLine(null, leftover, opts, stylize);
  1222. leftover = '';
  1223. }
  1224. callback(code);
  1225. }
  1226. dtrace.stdout.setEncoding('utf8');
  1227. dtrace.stdout.on('data', function (chunk) {
  1228. var lines = chunk.split(/\r\n|\n/);
  1229. var length = lines.length;
  1230. if (length === 1) {
  1231. leftover += lines[0];
  1232. return;
  1233. }
  1234. if (length > 1) {
  1235. handleLogLine(null, leftover + lines[0], opts, stylize);
  1236. }
  1237. leftover = lines.pop();
  1238. length -= 1;
  1239. for (var i = 1; i < length; i++) {
  1240. handleLogLine(null, lines[i], opts, stylize);
  1241. }
  1242. });
  1243. if (nodeSpawnSupportsStdio) {
  1244. dtrace.on('exit', finish);
  1245. } else {
  1246. // Fallback (for < v0.8) to pipe the dtrace process' stderr to
  1247. // this stderr. Wait for all of (1) process 'exit', (2) stderr
  1248. // 'end', and (2) stdout 'end' before returning to ensure all
  1249. // stderr is flushed (issue #54).
  1250. var returnCode = null;
  1251. var eventsRemaining = 3;
  1252. function countdownToFinish(code) {
  1253. returnCode = code;
  1254. eventsRemaining--;
  1255. if (eventsRemaining == 0) {
  1256. finish(returnCode);
  1257. }
  1258. }
  1259. dtrace.stderr.pipe(process.stderr);
  1260. dtrace.stderr.on('end', countdownToFinish);
  1261. dtrace.stderr.on('end', countdownToFinish);
  1262. dtrace.on('exit', countdownToFinish);
  1263. }
  1264. });
  1265. }
  1266. /**
  1267. * Process all input from the given log file.
  1268. *
  1269. * @param file {String} Log file path to process.
  1270. * @params opts {Object} Bunyan options object.
  1271. * @param stylize {Function} Output stylize function to use.
  1272. * @param callback {Function} `function ()`
  1273. */
  1274. function processFile(file, opts, stylize, callback) {
  1275. var stream = fs.createReadStream(file);
  1276. if (/\.gz$/.test(file)) {
  1277. stream = stream.pipe(require('zlib').createGunzip());
  1278. }
  1279. // Manually decode streams - lazy load here as per node/lib/fs.js
  1280. var decoder = new (require('string_decoder').StringDecoder)('utf8');
  1281. streams[file].stream = stream;
  1282. stream.on('error', function (err) {
  1283. streams[file].done = true;
  1284. callback(err);
  1285. });
  1286. var leftover = ''; // Left-over partial line from last chunk.
  1287. stream.on('data', function (data) {
  1288. var chunk = decoder.write(data);
  1289. if (!chunk.length) {
  1290. return;
  1291. }
  1292. var lines = chunk.split(/\r\n|\n/);
  1293. var length = lines.length;
  1294. if (length === 1) {
  1295. leftover += lines[0];
  1296. return;
  1297. }
  1298. if (length > 1) {
  1299. handleLogLine(file, leftover + lines[0], opts, stylize);
  1300. }
  1301. leftover = lines.pop();
  1302. length -= 1;
  1303. for (var i = 1; i < length; i++) {
  1304. handleLogLine(file, lines[i], opts, stylize);
  1305. }
  1306. });
  1307. stream.on('end', function () {
  1308. streams[file].done = true;
  1309. if (leftover) {
  1310. handleLogLine(file, leftover, opts, stylize);
  1311. leftover = '';
  1312. } else {
  1313. emitNextRecord(opts, stylize);
  1314. }
  1315. callback();
  1316. });
  1317. }
  1318. /**
  1319. * From node async module.
  1320. */
  1321. /* BEGIN JSSTYLED */
  1322. function asyncForEach(arr, iterator, callback) {
  1323. callback = callback || function () {};
  1324. if (!arr.length) {
  1325. return callback();
  1326. }
  1327. var completed = 0;
  1328. arr.forEach(function (x) {
  1329. iterator(x, function (err) {
  1330. if (err) {
  1331. callback(err);
  1332. callback = function () {};
  1333. }
  1334. else {
  1335. completed += 1;
  1336. if (completed === arr.length) {
  1337. callback();
  1338. }
  1339. }
  1340. });
  1341. });
  1342. };
  1343. /* END JSSTYLED */
  1344. /**
  1345. * Cleanup and exit properly.
  1346. *
  1347. * Warning: this doesn't stop processing, i.e. process exit might be delayed.
  1348. * It is up to the caller to ensure that no subsequent bunyan processing
  1349. * is done after calling this.
  1350. *
  1351. * @param code {Number} exit code.
  1352. * @param signal {String} Optional signal name, if this was exitting because
  1353. * of a signal.
  1354. */
  1355. var cleanedUp = false;
  1356. function cleanupAndExit(code, signal) {
  1357. // Guard one call.
  1358. if (cleanedUp) {
  1359. return;
  1360. }
  1361. cleanedUp = true;
  1362. if (_DEBUG) warn('(bunyan: cleanupAndExit)');
  1363. // Clear possibly interrupted ANSI code (issue #59).
  1364. if (usingAnsiCodes) {
  1365. stdout.write('\033[0m');
  1366. }
  1367. // Kill possible dtrace child.
  1368. if (child) {
  1369. child.kill(signal);
  1370. }
  1371. if (pager) {
  1372. // Let pager know that output is done, then wait for pager to exit.
  1373. stdout.end();
  1374. pager.on('exit', function (pagerCode) {
  1375. if (_DEBUG)
  1376. warn('(bunyan: pager exit -> process.exit(%s))',
  1377. pagerCode || code);
  1378. process.exit(pagerCode || code);
  1379. });
  1380. } else {
  1381. if (_DEBUG) warn('(bunyan: process.exit(%s))', code);
  1382. process.exit(code);
  1383. }
  1384. }
  1385. //---- mainline
  1386. process.on('SIGINT', function () { cleanupAndExit(1, 'SIGINT'); });
  1387. process.on('SIGQUIT', function () { cleanupAndExit(1, 'SIGQUIT'); });
  1388. process.on('SIGTERM', function () { cleanupAndExit(1, 'SIGTERM'); });
  1389. process.on('SIGHUP', function () { cleanupAndExit(1, 'SIGHUP'); });
  1390. process.on('uncaughtException', function (err) {
  1391. function _indent(s) {
  1392. var lines = s.split(/\r?\n/);
  1393. for (var i = 0; i < lines.length; i++) {
  1394. lines[i] = '* ' + lines[i];
  1395. }
  1396. return lines.join('\n');
  1397. }
  1398. var title = encodeURIComponent(format(
  1399. 'Bunyan %s crashed: %s', getVersion(), String(err)));
  1400. var e = console.error;
  1401. e('```');
  1402. e('* The Bunyan CLI crashed!');
  1403. e('*');
  1404. if (err.name === 'ReferenceError' && gUsingConditionOpts) {
  1405. /* BEGIN JSSTYLED */
  1406. e('* This crash was due to a "ReferenceError", which is often the result of given');
  1407. e('* `-c CONDITION` code that doesn\'t guard against undefined values. If that is');
  1408. /* END JSSTYLED */
  1409. e('* not the problem:');
  1410. e('*');
  1411. }
  1412. e('* Please report this issue and include the details below:');
  1413. e('*');
  1414. e('* https://github.com/trentm/node-bunyan/issues/new?title=%s', title);
  1415. e('*');
  1416. e('* * *');
  1417. e('* platform:', process.platform);
  1418. e('* node version:', process.version);
  1419. e('* bunyan version:', getVersion());
  1420. e('* argv: %j', process.argv);
  1421. e('* log line: %j', currLine);
  1422. e('* stack:');
  1423. e(_indent(err.stack));
  1424. e('```');
  1425. process.exit(1);
  1426. });
  1427. function main(argv) {
  1428. try {
  1429. var opts = parseArgv(argv);
  1430. } catch (e) {
  1431. warn('bunyan: error: %s', e.message);
  1432. return drainStdoutAndExit(1);
  1433. }
  1434. if (opts.help) {
  1435. printHelp();
  1436. return;
  1437. }
  1438. if (opts.version) {
  1439. console.log('bunyan ' + getVersion());
  1440. return;
  1441. }
  1442. if (opts.pids && opts.args.length > 0) {
  1443. warn('bunyan: error: can\'t use both "-p PID" (%s) and file (%s) args',
  1444. opts.pids, opts.args.join(' '));
  1445. return drainStdoutAndExit(1);
  1446. }
  1447. if (opts.color === null) {
  1448. if (process.env.BUNYAN_NO_COLOR &&
  1449. process.env.BUNYAN_NO_COLOR.length > 0) {
  1450. opts.color = false;
  1451. } else {
  1452. opts.color = process.stdout.isTTY;
  1453. }
  1454. }
  1455. usingAnsiCodes = opts.color; // intentionally global
  1456. var stylize = (opts.color ? stylizeWithColor : stylizeWithoutColor);
  1457. // Pager.
  1458. var paginate = (
  1459. process.stdout.isTTY &&
  1460. process.stdin.isTTY &&
  1461. !opts.pids && // Don't page if following process output.
  1462. opts.args.length > 0 && // Don't page if no file args to process.
  1463. process.platform !== 'win32' &&
  1464. (nodeVer[0] > 0 || nodeVer[1] >= 8) &&
  1465. (opts.paginate === true ||
  1466. (opts.paginate !== false &&
  1467. (!process.env.BUNYAN_NO_PAGER ||
  1468. process.env.BUNYAN_NO_PAGER.length === 0))));
  1469. if (paginate) {
  1470. var pagerCmd = process.env.PAGER || 'less';
  1471. /* JSSTYLED */
  1472. assert.ok(pagerCmd.indexOf('"') === -1 && pagerCmd.indexOf("'") === -1,
  1473. 'cannot parse PAGER quotes yet');
  1474. var argv = pagerCmd.split(/\s+/g);
  1475. var env = objCopy(process.env);
  1476. if (env.LESS === undefined) {
  1477. // git's default is LESS=FRSX. I don't like the 'S' here because
  1478. // lines are *typically* wide with bunyan output and scrolling
  1479. // horizontally is a royal pain. Note a bug in Mac's `less -F`,
  1480. // such that SIGWINCH can kill it. If that rears too much then
  1481. // I'll remove 'F' from here.
  1482. env.LESS = 'FRX';
  1483. }
  1484. if (_DEBUG) warn('(pager: argv=%j, env.LESS=%j)', argv, env.LESS);
  1485. // `pager` and `stdout` intentionally global.
  1486. pager = spawn(argv[0], argv.slice(1),
  1487. // Share the stderr handle to have error output come
  1488. // straight through. Only supported in v0.8+.
  1489. {env: env, stdio: ['pipe', 1, 2]});
  1490. stdout = pager.stdin;
  1491. // Early termination of the pager: just stop.
  1492. pager.on('exit', function (pagerCode) {
  1493. if (_DEBUG) warn('(bunyan: pager exit)');
  1494. pager = null;
  1495. stdout.end()
  1496. stdout = process.stdout;
  1497. cleanupAndExit(pagerCode);
  1498. });
  1499. }
  1500. // Stdout error handling. (Couldn't setup until `stdout` was determined.)
  1501. stdout.on('error', function (err) {
  1502. if (_DEBUG) warn('(stdout error event: %s)', err);
  1503. if (err.code === 'EPIPE') {
  1504. drainStdoutAndExit(0);
  1505. } else if (err.toString() === 'Error: This socket is closed.') {
  1506. // Could get this if the pager closes its stdin, but hasn't
  1507. // exited yet.
  1508. drainStdoutAndExit(1);
  1509. } else {
  1510. warn(err);
  1511. drainStdoutAndExit(1);
  1512. }
  1513. });
  1514. var retval = 0;
  1515. if (opts.pids) {
  1516. processPids(opts, stylize, function (code) {
  1517. cleanupAndExit(code);
  1518. });
  1519. } else if (opts.args.length > 0) {
  1520. var files = opts.args;
  1521. files.forEach(function (file) {
  1522. streams[file] = { stream: null, records: [], done: false }
  1523. });
  1524. asyncForEach(files,
  1525. function (file, next) {
  1526. processFile(file, opts, stylize, function (err) {
  1527. if (err) {
  1528. warn('bunyan: %s', err.message);
  1529. retval += 1;
  1530. }
  1531. next();
  1532. });
  1533. },
  1534. function (err) {
  1535. if (err) {
  1536. warn('bunyan: unexpected error: %s', err.stack || err);
  1537. return drainStdoutAndExit(1);
  1538. }
  1539. cleanupAndExit(retval);
  1540. }
  1541. );
  1542. } else {
  1543. processStdin(opts, stylize, function () {
  1544. cleanupAndExit(retval);
  1545. });
  1546. }
  1547. }
  1548. if (require.main === module) {
  1549. // HACK guard for <https://github.com/trentm/json/issues/24>.
  1550. // We override the `process.stdout.end` guard that core node.js puts in
  1551. // place. The real fix is that `.end()` shouldn't be called on stdout
  1552. // in node core. Node v0.6.9 fixes that. Only guard for v0.6.0..v0.6.8.
  1553. if ([0, 6, 0] <= nodeVer && nodeVer <= [0, 6, 8]) {
  1554. var stdout = process.stdout;
  1555. stdout.end = stdout.destroy = stdout.destroySoon = function () {
  1556. /* pass */
  1557. };
  1558. }
  1559. main(process.argv);
  1560. }