|
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708 |
- #!/usr/bin/env node
- /**
- * Copyright 2017 Trent Mick
- * Copyright 2017 Joyent Inc.
- *
- * bunyan -- filter and pretty-print Bunyan log files (line-delimited JSON)
- *
- * See <https://github.com/trentm/node-bunyan>.
- *
- * -*- mode: js -*-
- * vim: expandtab:ts=4:sw=4
- */
-
- var VERSION = '1.8.12';
-
- var p = console.log;
- var util = require('util');
- var pathlib = require('path');
- var vm = require('vm');
- var http = require('http');
- var fs = require('fs');
- var warn = console.warn;
- var child_process = require('child_process'),
- spawn = child_process.spawn,
- exec = child_process.exec,
- execFile = child_process.execFile;
- var assert = require('assert');
-
- try {
- var moment = require('moment');
- } catch (e) {
- moment = null;
- }
-
-
- //---- globals and constants
-
- var nodeVer = process.versions.node.split('.').map(Number);
- var nodeSpawnSupportsStdio = (nodeVer[0] > 0 || nodeVer[1] >= 8);
-
- // Internal debug logging via `console.warn`.
- var _DEBUG = false;
-
- // Output modes.
- var OM_LONG = 1;
- var OM_JSON = 2;
- var OM_INSPECT = 3;
- var OM_SIMPLE = 4;
- var OM_SHORT = 5;
- var OM_BUNYAN = 6;
- var OM_FROM_NAME = {
- 'long': OM_LONG,
- 'paul': OM_LONG, /* backward compat */
- 'json': OM_JSON,
- 'inspect': OM_INSPECT,
- 'simple': OM_SIMPLE,
- 'short': OM_SHORT,
- 'bunyan': OM_BUNYAN
- };
-
-
- // Levels
- var TRACE = 10;
- var DEBUG = 20;
- var INFO = 30;
- var WARN = 40;
- var ERROR = 50;
- var FATAL = 60;
-
- var levelFromName = {
- 'trace': TRACE,
- 'debug': DEBUG,
- 'info': INFO,
- 'warn': WARN,
- 'error': ERROR,
- 'fatal': FATAL
- };
- var nameFromLevel = {};
- var upperNameFromLevel = {};
- var upperPaddedNameFromLevel = {};
- Object.keys(levelFromName).forEach(function (name) {
- var lvl = levelFromName[name];
- nameFromLevel[lvl] = name;
- upperNameFromLevel[lvl] = name.toUpperCase();
- upperPaddedNameFromLevel[lvl] = (
- name.length === 4 ? ' ' : '') + name.toUpperCase();
- });
-
-
- // Display time formats.
- var TIME_UTC = 1; // the default, bunyan's native format
- var TIME_LOCAL = 2;
-
- // Timezone formats: output format -> momentjs format string
- var TIMEZONE_UTC_FORMATS = {
- long: '[[]YYYY-MM-DD[T]HH:mm:ss.SSS[Z][]]',
- short: 'HH:mm:ss.SSS[Z]'
- };
- var TIMEZONE_LOCAL_FORMATS = {
- long: '[[]YYYY-MM-DD[T]HH:mm:ss.SSSZ[]]',
- short: 'HH:mm:ss.SSS'
- };
-
-
- // The current raw input line being processed. Used for `uncaughtException`.
- var currLine = null;
-
- // Child dtrace process, if any. Used for signal-handling.
- var child = null;
-
- // Whether ANSI codes are being used. Used for signal-handling.
- var usingAnsiCodes = false;
-
- // Used to tell the 'uncaughtException' handler that '-c CODE' is being used.
- var gUsingConditionOpts = false;
-
- // Pager child process, and output stream to which to write.
- var pager = null;
- var stdout = process.stdout;
-
-
-
- //---- support functions
-
- function getVersion() {
- return VERSION;
- }
-
-
- var format = util.format;
- if (!format) {
- /* BEGIN JSSTYLED */
- // If not node 0.6, then use its `util.format`:
- // <https://github.com/joyent/node/blob/master/lib/util.js#L22>:
- var inspect = util.inspect;
- var formatRegExp = /%[sdj%]/g;
- format = function format(f) {
- if (typeof f !== 'string') {
- var objects = [];
- for (var i = 0; i < arguments.length; i++) {
- objects.push(inspect(arguments[i]));
- }
- return objects.join(' ');
- }
-
- var i = 1;
- var args = arguments;
- var len = args.length;
- var str = String(f).replace(formatRegExp, function (x) {
- if (i >= len)
- return x;
- switch (x) {
- case '%s': return String(args[i++]);
- case '%d': return Number(args[i++]);
- case '%j': return JSON.stringify(args[i++]);
- case '%%': return '%';
- default:
- return x;
- }
- });
- for (var x = args[i]; i < len; x = args[++i]) {
- if (x === null || typeof x !== 'object') {
- str += ' ' + x;
- } else {
- str += ' ' + inspect(x);
- }
- }
- return str;
- };
- /* END JSSTYLED */
- }
-
- function indent(s) {
- return ' ' + s.split(/\r?\n/).join('\n ');
- }
-
- function objCopy(obj) {
- if (obj === null) {
- return null;
- } else if (Array.isArray(obj)) {
- return obj.slice();
- } else {
- var copy = {};
- Object.keys(obj).forEach(function (k) {
- copy[k] = obj[k];
- });
- return copy;
- }
- }
-
- function printHelp() {
- /* BEGIN JSSTYLED */
- p('Usage:');
- p(' bunyan [OPTIONS] [FILE ...]');
- p(' ... | bunyan [OPTIONS]');
- p(' bunyan [OPTIONS] -p PID');
- p('');
- p('Filter and pretty-print Bunyan log file content.');
- p('');
- p('General options:');
- p(' -h, --help print this help info and exit');
- p(' --version print version of this command and exit');
- p('');
- p('Runtime log snooping (via DTrace, only on supported platforms):');
- p(' -p PID Process bunyan:log-* probes from the process');
- p(' with the given PID. Can be used multiple times,');
- p(' or specify all processes with "*", or a set of');
- p(' processes whose command & args match a pattern');
- p(' with "-p NAME".');
- p('');
- p('Filtering options:');
- p(' -l, --level LEVEL');
- p(' Only show messages at or above the specified level.');
- p(' You can specify level *names* or the internal numeric');
- p(' values.');
- p(' -c, --condition CONDITION');
- p(' Run each log message through the condition and');
- p(' only show those that return truish. E.g.:');
- p(' -c \'this.pid == 123\'');
- p(' -c \'this.level == DEBUG\'');
- p(' -c \'this.msg.indexOf("boom") != -1\'');
- p(' "CONDITION" must be legal JS code. `this` holds');
- p(' the log record. The TRACE, DEBUG, ... FATAL values');
- p(' are defined to help with comparing `this.level`.');
- p(' --strict Suppress all but legal Bunyan JSON log lines. By default');
- p(' non-JSON, and non-Bunyan lines are passed through.');
- p('');
- p('Output options:');
- p(' --pager Pipe output into `less` (or $PAGER if set), if');
- p(' stdout is a TTY. This overrides $BUNYAN_NO_PAGER.');
- p(' Note: Paging is only supported on node >=0.8.');
- p(' --no-pager Do not pipe output into a pager.');
- p(' --color Colorize output. Defaults to try if output');
- p(' stream is a TTY.');
- p(' --no-color Force no coloring (e.g. terminal doesn\'t support it)');
- p(' -o, --output MODE');
- p(' Specify an output mode/format. One of');
- p(' long: (the default) pretty');
- p(' json: JSON output, 2-space indent');
- p(' json-N: JSON output, N-space indent, e.g. "json-4"');
- p(' bunyan: 0 indented JSON, bunyan\'s native format');
- p(' inspect: node.js `util.inspect` output');
- p(' short: like "long", but more concise');
- p(' simple: level, followed by "-" and then the message');
- p(' -j shortcut for `-o json`');
- p(' -0 shortcut for `-o bunyan`');
- p(' -L, --time local');
- p(' Display time field in local time, rather than UTC.');
- p('');
- p('Environment Variables:');
- p(' BUNYAN_NO_COLOR Set to a non-empty value to force no output ');
- p(' coloring. See "--no-color".');
- p(' BUNYAN_NO_PAGER Disable piping output to a pager. ');
- p(' See "--no-pager".');
- p('');
- p('See <https://github.com/trentm/node-bunyan> for more complete docs.');
- p('Please report bugs to <https://github.com/trentm/node-bunyan/issues>.');
- /* END JSSTYLED */
- }
-
- /*
- * If the user specifies multiple input sources, we want to print out records
- * from all sources in a single, chronologically ordered stream. To do this
- * efficiently, we first assume that all records within each source are ordered
- * already, so we need only keep track of the next record in each source and
- * the time of the last record emitted. To avoid excess memory usage, we
- * pause() streams that are ahead of others.
- *
- * 'streams' is an object indexed by source name (file name) which specifies:
- *
- * stream Actual stream object, so that we can pause and resume it.
- *
- * records Array of log records we've read, but not yet emitted. Each
- * record includes 'line' (the raw line), 'rec' (the JSON
- * record), and 'time' (the parsed time value).
- *
- * done Whether the stream has any more records to emit.
- */
- var streams = {};
-
- function gotRecord(file, line, rec, opts, stylize)
- {
- var time = new Date(rec.time);
-
- streams[file]['records'].push({ line: line, rec: rec, time: time });
- emitNextRecord(opts, stylize);
- }
-
- function filterRecord(rec, opts)
- {
- if (opts.level && rec.level < opts.level) {
- return false;
- }
-
- if (opts.condFuncs) {
- var recCopy = objCopy(rec);
- for (var i = 0; i < opts.condFuncs.length; i++) {
- var pass = opts.condFuncs[i].call(recCopy);
- if (!pass)
- return false;
- }
- } else if (opts.condVm) {
- for (var i = 0; i < opts.condVm.length; i++) {
- var pass = opts.condVm[i].runInNewContext(rec);
- if (!pass)
- return false;
- }
- }
-
- return true;
- }
-
- function emitNextRecord(opts, stylize)
- {
- var ofile, ready, minfile, rec;
-
- for (;;) {
- /*
- * Take a first pass through the input streams to see if we have a
- * record from all of them. If not, we'll pause any streams for
- * which we do already have a record (to avoid consuming excess
- * memory) and then wait until we have records from the others
- * before emitting the next record.
- *
- * As part of the same pass, we look for the earliest record
- * we have not yet emitted.
- */
- minfile = undefined;
- ready = true;
- for (ofile in streams) {
-
- if (streams[ofile].stream === null ||
- (!streams[ofile].done && streams[ofile].records.length === 0)) {
- ready = false;
- break;
- }
-
- if (streams[ofile].records.length > 0 &&
- (minfile === undefined ||
- streams[minfile].records[0].time >
- streams[ofile].records[0].time)) {
- minfile = ofile;
- }
- }
-
- if (!ready || minfile === undefined) {
- for (ofile in streams) {
- if (!streams[ofile].stream || streams[ofile].done)
- continue;
-
- if (streams[ofile].records.length > 0) {
- if (!streams[ofile].paused) {
- streams[ofile].paused = true;
- streams[ofile].stream.pause();
- }
- } else if (streams[ofile].paused) {
- streams[ofile].paused = false;
- streams[ofile].stream.resume();
- }
- }
-
- return;
- }
-
- /*
- * Emit the next record for 'minfile', and invoke ourselves again to
- * make sure we emit as many records as we can right now.
- */
- rec = streams[minfile].records.shift();
- emitRecord(rec.rec, rec.line, opts, stylize);
- }
- }
-
- /**
- * Return a function for the given JS code that returns.
- *
- * If no 'return' in the given javascript snippet, then assume we are a single
- * statement and wrap in 'return (...)'. This is for convenience for short
- * '-c ...' snippets.
- */
- function funcWithReturnFromSnippet(js) {
- // auto-"return"
- if (js.indexOf('return') === -1) {
- if (js.substring(js.length - 1) === ';') {
- js = js.substring(0, js.length - 1);
- }
- js = 'return (' + js + ')';
- }
-
- // Expose level definitions to condition func context
- var varDefs = [];
- Object.keys(upperNameFromLevel).forEach(function (lvl) {
- varDefs.push(format('var %s = %d;',
- upperNameFromLevel[lvl], lvl));
- });
- varDefs = varDefs.join('\n') + '\n';
-
- return (new Function(varDefs + js));
- }
-
- /**
- * Parse the command-line options and arguments into an object.
- *
- * {
- * 'args': [...] // arguments
- * 'help': true, // true if '-h' option given
- * // etc.
- * }
- *
- * @return {Object} The parsed options. `.args` is the argument list.
- * @throws {Error} If there is an error parsing argv.
- */
- function parseArgv(argv) {
- var parsed = {
- args: [],
- help: false,
- color: null,
- paginate: null,
- outputMode: OM_LONG,
- jsonIndent: 2,
- level: null,
- strict: false,
- pids: null,
- pidsType: null,
- timeFormat: TIME_UTC // one of the TIME_ constants
- };
-
- // Turn '-iH' into '-i -H', except for argument-accepting options.
- var args = argv.slice(2); // drop ['node', 'scriptname']
- var newArgs = [];
- var optTakesArg = {'d': true, 'o': true, 'c': true, 'l': true, 'p': true};
- for (var i = 0; i < args.length; i++) {
- if (args[i].charAt(0) === '-' && args[i].charAt(1) !== '-' &&
- args[i].length > 2)
- {
- var splitOpts = args[i].slice(1).split('');
- for (var j = 0; j < splitOpts.length; j++) {
- newArgs.push('-' + splitOpts[j]);
- if (optTakesArg[splitOpts[j]]) {
- var optArg = splitOpts.slice(j+1).join('');
- if (optArg.length) {
- newArgs.push(optArg);
- }
- break;
- }
- }
- } else {
- newArgs.push(args[i]);
- }
- }
- args = newArgs;
-
- // Expose level definitions to condition vm context
- var condDefines = [];
- Object.keys(upperNameFromLevel).forEach(function (lvl) {
- condDefines.push(
- format('Object.prototype.%s = %s;', upperNameFromLevel[lvl], lvl));
- });
- condDefines = condDefines.join('\n') + '\n';
-
- var endOfOptions = false;
- while (args.length > 0) {
- var arg = args.shift();
- switch (arg) {
- case '--':
- endOfOptions = true;
- break;
- case '-h': // display help and exit
- case '--help':
- parsed.help = true;
- break;
- case '--version':
- parsed.version = true;
- break;
- case '--strict':
- parsed.strict = true;
- break;
- case '--color':
- parsed.color = true;
- break;
- case '--no-color':
- parsed.color = false;
- break;
- case '--pager':
- parsed.paginate = true;
- break;
- case '--no-pager':
- parsed.paginate = false;
- break;
- case '-o':
- case '--output':
- var name = args.shift();
- var idx = name.lastIndexOf('-');
- if (idx !== -1) {
- var indentation = Number(name.slice(idx+1));
- if (! isNaN(indentation)) {
- parsed.jsonIndent = indentation;
- name = name.slice(0, idx);
- }
- }
- parsed.outputMode = OM_FROM_NAME[name];
- if (parsed.outputMode === undefined) {
- throw new Error('unknown output mode: "'+name+'"');
- }
- break;
- case '-j': // output with JSON.stringify
- parsed.outputMode = OM_JSON;
- break;
- case '-0':
- parsed.outputMode = OM_BUNYAN;
- break;
- case '-L':
- parsed.timeFormat = TIME_LOCAL;
- if (!moment) {
- throw new Error(
- 'could not find moment package required for "-L"');
- }
- break;
- case '--time':
- var timeArg = args.shift();
- switch (timeArg) {
- case 'utc':
- parsed.timeFormat = TIME_UTC;
- break
- case 'local':
- parsed.timeFormat = TIME_LOCAL;
- if (!moment) {
- throw new Error('could not find moment package '
- + 'required for "--time=local"');
- }
- break
- case undefined:
- throw new Error('missing argument to "--time"');
- default:
- throw new Error(format('invalid time format: "%s"',
- timeArg));
- }
- break;
- case '-p':
- if (!parsed.pids) {
- parsed.pids = [];
- }
- var pidArg = args.shift();
- var pid = +(pidArg);
- if (!isNaN(pid) || pidArg === '*') {
- if (parsed.pidsType && parsed.pidsType !== 'num') {
- throw new Error(format('cannot mix PID name and '
- + 'number arguments: "%s"', pidArg));
- }
- parsed.pidsType = 'num';
- if (!parsed.pids) {
- parsed.pids = [];
- }
- parsed.pids.push(isNaN(pid) ? pidArg : pid);
- } else {
- if (parsed.pidsType && parsed.pidsType !== 'name') {
- throw new Error(format('cannot mix PID name and '
- + 'number arguments: "%s"', pidArg));
- }
- parsed.pidsType = 'name';
- parsed.pids = pidArg;
- }
- break;
- case '-l':
- case '--level':
- var levelArg = args.shift();
- var level = +(levelArg);
- if (isNaN(level)) {
- level = +levelFromName[levelArg.toLowerCase()];
- }
- if (isNaN(level)) {
- throw new Error('unknown level value: "'+levelArg+'"');
- }
- parsed.level = level;
- break;
- case '-c':
- case '--condition':
- gUsingConditionOpts = true;
- var condition = args.shift();
- if (Boolean(process.env.BUNYAN_EXEC &&
- process.env.BUNYAN_EXEC === 'vm'))
- {
- parsed.condVm = parsed.condVm || [];
- var scriptName = 'bunyan-condition-'+parsed.condVm.length;
- var code = condDefines + condition;
- var script;
- try {
- script = vm.createScript(code, scriptName);
- } catch (complErr) {
- throw new Error(format('illegal CONDITION code: %s\n'
- + ' CONDITION script:\n'
- + '%s\n'
- + ' Error:\n'
- + '%s',
- complErr, indent(code), indent(complErr.stack)));
- }
-
- // Ensure this is a reasonably safe CONDITION.
- try {
- script.runInNewContext(minValidRecord);
- } catch (condErr) {
- throw new Error(format(
- /* JSSTYLED */
- 'CONDITION code cannot safely filter a minimal Bunyan log record\n'
- + ' CONDITION script:\n'
- + '%s\n'
- + ' Minimal Bunyan log record:\n'
- + '%s\n'
- + ' Filter error:\n'
- + '%s',
- indent(code),
- indent(JSON.stringify(minValidRecord, null, 2)),
- indent(condErr.stack)
- ));
- }
- parsed.condVm.push(script);
- } else {
- parsed.condFuncs = parsed.condFuncs || [];
- parsed.condFuncs.push(funcWithReturnFromSnippet(condition));
- }
- break;
- default: // arguments
- if (!endOfOptions && arg.length > 0 && arg[0] === '-') {
- throw new Error('unknown option "'+arg+'"');
- }
- parsed.args.push(arg);
- break;
- }
- }
- //TODO: '--' handling and error on a first arg that looks like an option.
-
- return parsed;
- }
-
-
- function isInteger(s) {
- return (s.search(/^-?[0-9]+$/) == 0);
- }
-
-
- // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
- // Suggested colors (some are unreadable in common cases):
- // - Good: cyan, yellow (limited use), bold, green, magenta, red
- // - Bad: blue (not visible on cmd.exe), grey (same color as background on
- // Solarized Dark theme from <https://github.com/altercation/solarized>, see
- // issue #160)
- var colors = {
- 'bold' : [1, 22],
- 'italic' : [3, 23],
- 'underline' : [4, 24],
- 'inverse' : [7, 27],
- 'white' : [37, 39],
- 'grey' : [90, 39],
- 'black' : [30, 39],
- 'blue' : [34, 39],
- 'cyan' : [36, 39],
- 'green' : [32, 39],
- 'magenta' : [35, 39],
- 'red' : [31, 39],
- 'yellow' : [33, 39]
- };
-
- function stylizeWithColor(str, color) {
- if (!str)
- return '';
- var codes = colors[color];
- if (codes) {
- return '\033[' + codes[0] + 'm' + str +
- '\033[' + codes[1] + 'm';
- } else {
- return str;
- }
- }
-
- function stylizeWithoutColor(str, color) {
- return str;
- }
-
-
- /**
- * Is this a valid Bunyan log record.
- */
- function isValidRecord(rec) {
- if (rec.v == null ||
- rec.level == null ||
- rec.name == null ||
- rec.hostname == null ||
- rec.pid == null ||
- rec.time == null ||
- rec.msg == null) {
- // Not valid Bunyan log.
- return false;
- } else {
- return true;
- }
- }
- var minValidRecord = {
- v: 0, //TODO: get this from bunyan.LOG_VERSION
- level: INFO,
- name: 'name',
- hostname: 'hostname',
- pid: 123,
- time: Date.now(),
- msg: 'msg'
- };
-
-
- /**
- * Parses the given log line and either emits it right away (for invalid
- * records) or enqueues it for emitting later when it's the next line to show.
- */
- function handleLogLine(file, line, opts, stylize) {
- currLine = line; // intentionally global
-
- // Emit non-JSON lines immediately.
- var rec;
- if (!line) {
- if (!opts.strict) emit(line + '\n');
- return;
- } else if (line[0] !== '{') {
- if (!opts.strict) emit(line + '\n'); // not JSON
- return;
- } else {
- try {
- rec = JSON.parse(line);
- } catch (e) {
- if (!opts.strict) emit(line + '\n');
- return;
- }
- }
-
- if (!isValidRecord(rec)) {
- if (!opts.strict) emit(line + '\n');
- return;
- }
-
- if (!filterRecord(rec, opts))
- return;
-
- if (file === null)
- return emitRecord(rec, line, opts, stylize);
-
- return gotRecord(file, line, rec, opts, stylize);
- }
-
- /**
- * Print out a single result, considering input options.
- */
- function emitRecord(rec, line, opts, stylize) {
- var short = false;
-
- switch (opts.outputMode) {
- case OM_SHORT:
- short = true;
- /* jsl:fall-thru */
-
- case OM_LONG:
- // [time] LEVEL: name[/comp]/pid on hostname (src): msg* (extras...)
- // msg*
- // --
- // long and multi-line extras
- // ...
- // If 'msg' is single-line, then it goes in the top line.
- // If 'req', show the request.
- // If 'res', show the response.
- // If 'err' and 'err.stack' then show that.
- if (!isValidRecord(rec)) {
- return emit(line + '\n');
- }
-
- delete rec.v;
-
- // Time.
- var time;
- if (!short && opts.timeFormat === TIME_UTC) {
- // Fast default path: We assume the raw `rec.time` is a UTC time
- // in ISO 8601 format (per spec).
- time = '[' + rec.time + ']';
- } else if (!moment && opts.timeFormat === TIME_UTC) {
- // Don't require momentjs install, as long as not using TIME_LOCAL.
- time = rec.time.substr(11);
- } else {
- var tzFormat;
- var moTime = moment(rec.time);
- switch (opts.timeFormat) {
- case TIME_UTC:
- tzFormat = TIMEZONE_UTC_FORMATS[short ? 'short' : 'long'];
- moTime.utc();
- break;
- case TIME_LOCAL:
- tzFormat = TIMEZONE_LOCAL_FORMATS[short ? 'short' : 'long'];
- break;
- default:
- throw new Error('unexpected timeFormat: ' + opts.timeFormat);
- };
- time = moTime.format(tzFormat);
- }
- time = stylize(time, 'none');
- delete rec.time;
-
- var nameStr = rec.name;
- delete rec.name;
-
- if (rec.component) {
- nameStr += '/' + rec.component;
- }
- delete rec.component;
-
- if (!short)
- nameStr += '/' + rec.pid;
- delete rec.pid;
-
- var level = (upperPaddedNameFromLevel[rec.level] || 'LVL' + rec.level);
- if (opts.color) {
- var colorFromLevel = {
- 10: 'white', // TRACE
- 20: 'yellow', // DEBUG
- 30: 'cyan', // INFO
- 40: 'magenta', // WARN
- 50: 'red', // ERROR
- 60: 'inverse', // FATAL
- };
- level = stylize(level, colorFromLevel[rec.level]);
- }
- delete rec.level;
-
- var src = '';
- if (rec.src && rec.src.file) {
- var s = rec.src;
- if (s.func) {
- src = format(' (%s:%d in %s)', s.file, s.line, s.func);
- } else {
- src = format(' (%s:%d)', s.file, s.line);
- }
- src = stylize(src, 'green');
- }
- delete rec.src;
-
- var hostname = rec.hostname;
- delete rec.hostname;
-
- var extras = [];
- var details = [];
-
- if (rec.req_id) {
- extras.push('req_id=' + rec.req_id);
- }
- delete rec.req_id;
-
- var onelineMsg;
- if (rec.msg.indexOf('\n') !== -1) {
- onelineMsg = '';
- details.push(indent(stylize(rec.msg, 'cyan')));
- } else {
- onelineMsg = ' ' + stylize(rec.msg, 'cyan');
- }
- delete rec.msg;
-
- if (rec.req && typeof (rec.req) === 'object') {
- var req = rec.req;
- delete rec.req;
- var headers = req.headers;
- if (!headers) {
- headers = '';
- } else if (typeof (headers) === 'string') {
- headers = '\n' + headers;
- } else if (typeof (headers) === 'object') {
- headers = '\n' + Object.keys(headers).map(function (h) {
- return h + ': ' + headers[h];
- }).join('\n');
- }
- var s = format('%s %s HTTP/%s%s', req.method,
- req.url,
- req.httpVersion || '1.1',
- headers
- );
- delete req.url;
- delete req.method;
- delete req.httpVersion;
- delete req.headers;
- if (req.body) {
- s += '\n\n' + (typeof (req.body) === 'object'
- ? JSON.stringify(req.body, null, 2) : req.body);
- delete req.body;
- }
- if (req.trailers && Object.keys(req.trailers) > 0) {
- s += '\n' + Object.keys(req.trailers).map(function (t) {
- return t + ': ' + req.trailers[t];
- }).join('\n');
- }
- delete req.trailers;
- details.push(indent(s));
- // E.g. for extra 'foo' field on 'req', add 'req.foo' at
- // top-level. This *does* have the potential to stomp on a
- // literal 'req.foo' key.
- Object.keys(req).forEach(function (k) {
- rec['req.' + k] = req[k];
- })
- }
-
- /*
- * `client_req` is the typical field name for an *HTTP client request
- * object* serialized by the restify-clients library. Render the
- * client request somewhat like `curl` debug output shows it.
- */
- if (rec.client_req && typeof (rec.client_req) === 'object') {
- var client_req = rec.client_req;
- delete rec.client_req;
-
- var headers = client_req.headers;
- delete client_req.headers;
-
- /*
- * `client_req.address`, and `client_req.port`, provide values for
- * a *likely* "Host" header that wasn't included in the serialized
- * headers. Node.js will often add this "Host" header in its
- * `http.ClientRequest`, e.g. for node v6.10.3:
- * // JSSTYLED
- * https://github.com/nodejs/node/blob/v6.10.3/lib/_http_client.js#L88-L105
- *
- * If `client_req.port` exists and is 80 or 443, we *assume* that
- * is the default protocol port, and elide it per the `defaultPort`
- * handling in the node.js link above.
- *
- * Note: This added Host header is a *guess*. Bunyan shouldn't be
- * doing this "favour" for users because it can be wrong and
- * misleading. Bunyan 2.x will drop adding this. See issue #504
- * for details.
- */
- var hostHeaderLine = '';
- if (!headers || !(
- Object.hasOwnProperty.call(headers, 'host') ||
- Object.hasOwnProperty.call(headers, 'Host') ||
- Object.hasOwnProperty.call(headers, 'HOST')
- )
- ) {
- if (Object.hasOwnProperty.call(client_req, 'address')) {
- hostHeaderLine = '\nHost: ' + client_req.address;
- if (Object.hasOwnProperty.call(client_req, 'port')) {
- // XXX
- var port = +client_req.port;
- if (port !== 80 && port !== 443) {
- hostHeaderLine += ':' + client_req.port;
- }
- delete client_req.port;
- }
- delete client_req.address;
- }
- }
-
- var s = format('%s %s HTTP/%s%s%s', client_req.method,
- client_req.url,
- client_req.httpVersion || '1.1',
- hostHeaderLine,
- (headers ?
- '\n' + Object.keys(headers).map(
- function (h) {
- return h + ': ' + headers[h];
- }).join('\n') :
- ''));
- delete client_req.method;
- delete client_req.url;
- delete client_req.httpVersion;
-
- if (client_req.body) {
- s += '\n\n' + (typeof (client_req.body) === 'object' ?
- JSON.stringify(client_req.body, null, 2) :
- client_req.body);
- delete client_req.body;
- }
- // E.g. for extra 'foo' field on 'client_req', add
- // 'client_req.foo' at top-level. This *does* have the potential
- // to stomp on a literal 'client_req.foo' key.
- Object.keys(client_req).forEach(function (k) {
- rec['client_req.' + k] = client_req[k];
- })
- details.push(indent(s));
- }
-
- function _res(res) {
- var s = '';
-
- /*
- * Handle `res.header` or `res.headers` as either a string or
- * an object of header key/value pairs. Prefer `res.header` if set,
- * because that's what Bunyan's own `res` serializer specifies,
- * because that's the value in Node.js's core HTTP server response
- * implementation that has all the implicit headers.
- *
- * Note: `res.header` (string) typically includes the 'HTTP/1.1 ...'
- * status line.
- */
- var headerTypes = {string: true, object: true};
- var headers;
- var headersStr = '';
- var headersHaveStatusLine = false;
- if (res.header && headerTypes[typeof (res.header)]) {
- headers = res.header;
- delete res.header;
- } else if (res.headers && headerTypes[typeof (res.headers)]) {
- headers = res.headers;
- delete res.headers;
- }
- if (headers === undefined) {
- /* pass through */
- } else if (typeof (headers) === 'string') {
- headersStr = headers.trimRight(); // Trim the CRLF.
- if (headersStr.slice(0, 5) === 'HTTP/') {
- headersHaveStatusLine = true;
- }
- } else {
- headersStr += Object.keys(headers).map(
- function (h) { return h + ': ' + headers[h]; }).join('\n');
- }
-
- /*
- * Add a 'HTTP/1.1 ...' status line if the headers didn't already
- * include it.
- */
- if (!headersHaveStatusLine && res.statusCode !== undefined) {
- s += format('HTTP/1.1 %s %s\n', res.statusCode,
- http.STATUS_CODES[res.statusCode]);
- }
- delete res.statusCode;
- s += headersStr;
-
- if (res.body !== undefined) {
- var body = (typeof (res.body) === 'object'
- ? JSON.stringify(res.body, null, 2) : res.body);
- if (body.length > 0) { s += '\n\n' + body };
- delete res.body;
- } else {
- s = s.trimRight();
- }
- if (res.trailer) {
- s += '\n' + res.trailer;
- }
- delete res.trailer;
- if (s) {
- details.push(indent(s));
- }
- // E.g. for extra 'foo' field on 'res', add 'res.foo' at
- // top-level. This *does* have the potential to stomp on a
- // literal 'res.foo' key.
- Object.keys(res).forEach(function (k) {
- rec['res.' + k] = res[k];
- });
- }
-
- if (rec.res && typeof (rec.res) === 'object') {
- _res(rec.res);
- delete rec.res;
- }
- if (rec.client_res && typeof (rec.client_res) === 'object') {
- _res(rec.client_res);
- delete rec.client_res;
- }
-
- if (rec.err && rec.err.stack) {
- var err = rec.err
- if (typeof (err.stack) !== 'string') {
- details.push(indent(err.stack.toString()));
- } else {
- details.push(indent(err.stack));
- }
- delete err.message;
- delete err.name;
- delete err.stack;
- // E.g. for extra 'foo' field on 'err', add 'err.foo' at
- // top-level. This *does* have the potential to stomp on a
- // literal 'err.foo' key.
- Object.keys(err).forEach(function (k) {
- rec['err.' + k] = err[k];
- })
- delete rec.err;
- }
-
- var leftover = Object.keys(rec);
- for (var i = 0; i < leftover.length; i++) {
- var key = leftover[i];
- var value = rec[key];
- var stringified = false;
- if (typeof (value) !== 'string') {
- value = JSON.stringify(value, null, 2);
- stringified = true;
- }
- if (value.indexOf('\n') !== -1 || value.length > 50) {
- details.push(indent(key + ': ' + value));
- } else if (!stringified && (value.indexOf(' ') != -1 ||
- value.length === 0))
- {
- extras.push(key + '=' + JSON.stringify(value));
- } else {
- extras.push(key + '=' + value);
- }
- }
-
- extras = stylize(
- (extras.length ? ' (' + extras.join(', ') + ')' : ''), 'none');
- details = stylize(
- (details.length ? details.join('\n --\n') + '\n' : ''), 'none');
- if (!short)
- emit(format('%s %s: %s on %s%s:%s%s\n%s',
- time,
- level,
- nameStr,
- hostname || '<no-hostname>',
- src,
- onelineMsg,
- extras,
- details));
- else
- emit(format('%s %s %s:%s%s\n%s',
- time,
- level,
- nameStr,
- onelineMsg,
- extras,
- details));
- break;
-
- case OM_INSPECT:
- emit(util.inspect(rec, false, Infinity, true) + '\n');
- break;
-
- case OM_BUNYAN:
- emit(JSON.stringify(rec, null, 0) + '\n');
- break;
-
- case OM_JSON:
- emit(JSON.stringify(rec, null, opts.jsonIndent) + '\n');
- break;
-
- case OM_SIMPLE:
- /* JSSTYLED */
- // <http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/SimpleLayout.html>
- if (!isValidRecord(rec)) {
- return emit(line + '\n');
- }
- emit(format('%s - %s\n',
- upperNameFromLevel[rec.level] || 'LVL' + rec.level,
- rec.msg));
- break;
- default:
- throw new Error('unknown output mode: '+opts.outputMode);
- }
- }
-
-
- var stdoutFlushed = true;
- function emit(s) {
- try {
- stdoutFlushed = stdout.write(s);
- } catch (e) {
- // Handle any exceptions in stdout writing in `stdout.on('error', ...)`.
- }
- }
-
-
- /**
- * A hacked up version of 'process.exit' that will first drain stdout
- * before exiting. *WARNING: This doesn't stop event processing.* IOW,
- * callers have to be careful that code following this call isn't
- * accidentally executed.
- *
- * In node v0.6 "process.stdout and process.stderr are blocking when they
- * refer to regular files or TTY file descriptors." However, this hack might
- * still be necessary in a shell pipeline.
- */
- function drainStdoutAndExit(code) {
- if (_DEBUG) warn('(drainStdoutAndExit(%d))', code);
- stdout.on('drain', function () {
- cleanupAndExit(code);
- });
- if (stdoutFlushed) {
- cleanupAndExit(code);
- }
- }
-
-
- /**
- * Process all input from stdin.
- *
- * @params opts {Object} Bunyan options object.
- * @param stylize {Function} Output stylize function to use.
- * @param callback {Function} `function ()`
- */
- function processStdin(opts, stylize, callback) {
- var leftover = ''; // Left-over partial line from last chunk.
- var stdin = process.stdin;
- stdin.resume();
- stdin.setEncoding('utf8');
- stdin.on('data', function (chunk) {
- var lines = chunk.split(/\r\n|\n/);
- var length = lines.length;
- if (length === 1) {
- leftover += lines[0];
- return;
- }
-
- if (length > 1) {
- handleLogLine(null, leftover + lines[0], opts, stylize);
- }
- leftover = lines.pop();
- length -= 1;
- for (var i = 1; i < length; i++) {
- handleLogLine(null, lines[i], opts, stylize);
- }
- });
- stdin.on('end', function () {
- if (leftover) {
- handleLogLine(null, leftover, opts, stylize);
- leftover = '';
- }
- callback();
- });
- }
-
-
- /**
- * Process bunyan:log-* probes from the given pid.
- *
- * @params opts {Object} Bunyan options object.
- * @param stylize {Function} Output stylize function to use.
- * @param callback {Function} `function (code)`
- */
- function processPids(opts, stylize, callback) {
- var leftover = ''; // Left-over partial line from last chunk.
-
- /**
- * Get the PIDs to dtrace.
- *
- * @param cb {Function} `function (errCode, pids)`
- */
- function getPids(cb) {
- if (opts.pidsType === 'num') {
- return cb(null, opts.pids);
- }
- if (process.platform === 'sunos') {
- execFile('/bin/pgrep', ['-lf', opts.pids],
- function (pidsErr, stdout, stderr) {
- if (pidsErr) {
- warn('bunyan: error getting PIDs for "%s": %s\n%s\n%s',
- opts.pids, pidsErr.message, stdout, stderr);
- return cb(1);
- }
- var pids = stdout.trim().split('\n')
- .map(function (line) {
- return line.trim().split(/\s+/)[0]
- })
- .filter(function (pid) {
- return Number(pid) !== process.pid
- });
- if (pids.length === 0) {
- warn('bunyan: error: no matching PIDs found for "%s"',
- opts.pids);
- return cb(2);
- }
- cb(null, pids);
- }
- );
- } else {
- var regex = opts.pids;
- if (regex && /[a-zA-Z0-9_]/.test(regex[0])) {
- // 'foo' -> '[f]oo' trick to exclude the 'grep' PID from its
- // own search.
- regex = '[' + regex[0] + ']' + regex.slice(1);
- }
- exec(format('ps -A -o pid,command | grep \'%s\'', regex),
- function (pidsErr, stdout, stderr) {
- if (pidsErr) {
- warn('bunyan: error getting PIDs for "%s": %s\n%s\n%s',
- opts.pids, pidsErr.message, stdout, stderr);
- return cb(1);
- }
- var pids = stdout.trim().split('\n')
- .map(function (line) {
- return line.trim().split(/\s+/)[0];
- })
- .filter(function (pid) {
- return Number(pid) !== process.pid;
- });
- if (pids.length === 0) {
- warn('bunyan: error: no matching PIDs found for "%s"',
- opts.pids);
- return cb(2);
- }
- cb(null, pids);
- }
- );
- }
- }
-
- getPids(function (errCode, pids) {
- if (errCode) {
- return callback(errCode);
- }
-
- var probes = pids.map(function (pid) {
- if (!opts.level)
- return format('bunyan%s:::log-*', pid);
-
- var rval = [], l;
-
- for (l in levelFromName) {
- if (levelFromName[l] >= opts.level)
- rval.push(format('bunyan%s:::log-%s', pid, l));
- }
-
- if (rval.length != 0)
- return rval.join(',');
-
- warn('bunyan: error: level (%d) exceeds maximum logging level',
- opts.level);
- return drainStdoutAndExit(1);
- }).join(',');
- var argv = ['dtrace', '-Z', '-x', 'strsize=4k',
- '-x', 'switchrate=10hz', '-qn',
- format('%s{printf("%s", copyinstr(arg0))}', probes)];
- //console.log('dtrace argv: %s', argv);
- var dtrace = spawn(argv[0], argv.slice(1),
- // Share the stderr handle to have error output come
- // straight through. Only supported in v0.8+.
- {stdio: ['pipe', 'pipe', process.stderr]});
- dtrace.on('error', function (e) {
- if (e.syscall === 'spawn' && e.errno === 'ENOENT') {
- console.error('bunyan: error: could not spawn "dtrace" ' +
- '("bunyan -p" is only supported on platforms with dtrace)');
- } else {
- console.error('bunyan: error: unexpected dtrace error: %s', e);
- }
- callback(1);
- })
- child = dtrace; // intentionally global
-
- function finish(code) {
- if (leftover) {
- handleLogLine(null, leftover, opts, stylize);
- leftover = '';
- }
- callback(code);
- }
-
- dtrace.stdout.setEncoding('utf8');
- dtrace.stdout.on('data', function (chunk) {
- var lines = chunk.split(/\r\n|\n/);
- var length = lines.length;
- if (length === 1) {
- leftover += lines[0];
- return;
- }
- if (length > 1) {
- handleLogLine(null, leftover + lines[0], opts, stylize);
- }
- leftover = lines.pop();
- length -= 1;
- for (var i = 1; i < length; i++) {
- handleLogLine(null, lines[i], opts, stylize);
- }
- });
-
- if (nodeSpawnSupportsStdio) {
- dtrace.on('exit', finish);
- } else {
- // Fallback (for < v0.8) to pipe the dtrace process' stderr to
- // this stderr. Wait for all of (1) process 'exit', (2) stderr
- // 'end', and (2) stdout 'end' before returning to ensure all
- // stderr is flushed (issue #54).
- var returnCode = null;
- var eventsRemaining = 3;
- function countdownToFinish(code) {
- returnCode = code;
- eventsRemaining--;
- if (eventsRemaining == 0) {
- finish(returnCode);
- }
- }
- dtrace.stderr.pipe(process.stderr);
- dtrace.stderr.on('end', countdownToFinish);
- dtrace.stderr.on('end', countdownToFinish);
- dtrace.on('exit', countdownToFinish);
- }
- });
- }
-
-
- /**
- * Process all input from the given log file.
- *
- * @param file {String} Log file path to process.
- * @params opts {Object} Bunyan options object.
- * @param stylize {Function} Output stylize function to use.
- * @param callback {Function} `function ()`
- */
- function processFile(file, opts, stylize, callback) {
- var stream = fs.createReadStream(file);
- if (/\.gz$/.test(file)) {
- stream = stream.pipe(require('zlib').createGunzip());
- }
- // Manually decode streams - lazy load here as per node/lib/fs.js
- var decoder = new (require('string_decoder').StringDecoder)('utf8');
-
- streams[file].stream = stream;
-
- stream.on('error', function (err) {
- streams[file].done = true;
- callback(err);
- });
-
- var leftover = ''; // Left-over partial line from last chunk.
- stream.on('data', function (data) {
- var chunk = decoder.write(data);
- if (!chunk.length) {
- return;
- }
- var lines = chunk.split(/\r\n|\n/);
- var length = lines.length;
- if (length === 1) {
- leftover += lines[0];
- return;
- }
-
- if (length > 1) {
- handleLogLine(file, leftover + lines[0], opts, stylize);
- }
- leftover = lines.pop();
- length -= 1;
- for (var i = 1; i < length; i++) {
- handleLogLine(file, lines[i], opts, stylize);
- }
- });
-
- stream.on('end', function () {
- streams[file].done = true;
- if (leftover) {
- handleLogLine(file, leftover, opts, stylize);
- leftover = '';
- } else {
- emitNextRecord(opts, stylize);
- }
- callback();
- });
- }
-
-
- /**
- * From node async module.
- */
- /* BEGIN JSSTYLED */
- function asyncForEach(arr, iterator, callback) {
- callback = callback || function () {};
- if (!arr.length) {
- return callback();
- }
- var completed = 0;
- arr.forEach(function (x) {
- iterator(x, function (err) {
- if (err) {
- callback(err);
- callback = function () {};
- }
- else {
- completed += 1;
- if (completed === arr.length) {
- callback();
- }
- }
- });
- });
- };
- /* END JSSTYLED */
-
-
-
- /**
- * Cleanup and exit properly.
- *
- * Warning: this doesn't stop processing, i.e. process exit might be delayed.
- * It is up to the caller to ensure that no subsequent bunyan processing
- * is done after calling this.
- *
- * @param code {Number} exit code.
- * @param signal {String} Optional signal name, if this was exitting because
- * of a signal.
- */
- var cleanedUp = false;
- function cleanupAndExit(code, signal) {
- // Guard one call.
- if (cleanedUp) {
- return;
- }
- cleanedUp = true;
- if (_DEBUG) warn('(bunyan: cleanupAndExit)');
-
- // Clear possibly interrupted ANSI code (issue #59).
- if (usingAnsiCodes) {
- stdout.write('\033[0m');
- }
-
- // Kill possible dtrace child.
- if (child) {
- child.kill(signal);
- }
-
- if (pager) {
- // Let pager know that output is done, then wait for pager to exit.
- stdout.end();
- pager.on('exit', function (pagerCode) {
- if (_DEBUG)
- warn('(bunyan: pager exit -> process.exit(%s))',
- pagerCode || code);
- process.exit(pagerCode || code);
- });
- } else {
- if (_DEBUG) warn('(bunyan: process.exit(%s))', code);
- process.exit(code);
- }
- }
-
-
-
- //---- mainline
-
- process.on('SIGINT', function () { cleanupAndExit(1, 'SIGINT'); });
- process.on('SIGQUIT', function () { cleanupAndExit(1, 'SIGQUIT'); });
- process.on('SIGTERM', function () { cleanupAndExit(1, 'SIGTERM'); });
- process.on('SIGHUP', function () { cleanupAndExit(1, 'SIGHUP'); });
-
- process.on('uncaughtException', function (err) {
- function _indent(s) {
- var lines = s.split(/\r?\n/);
- for (var i = 0; i < lines.length; i++) {
- lines[i] = '* ' + lines[i];
- }
- return lines.join('\n');
- }
-
- var title = encodeURIComponent(format(
- 'Bunyan %s crashed: %s', getVersion(), String(err)));
- var e = console.error;
- e('```');
- e('* The Bunyan CLI crashed!');
- e('*');
- if (err.name === 'ReferenceError' && gUsingConditionOpts) {
- /* BEGIN JSSTYLED */
- e('* This crash was due to a "ReferenceError", which is often the result of given');
- e('* `-c CONDITION` code that doesn\'t guard against undefined values. If that is');
- /* END JSSTYLED */
- e('* not the problem:');
- e('*');
- }
- e('* Please report this issue and include the details below:');
- e('*');
- e('* https://github.com/trentm/node-bunyan/issues/new?title=%s', title);
- e('*');
- e('* * *');
- e('* platform:', process.platform);
- e('* node version:', process.version);
- e('* bunyan version:', getVersion());
- e('* argv: %j', process.argv);
- e('* log line: %j', currLine);
- e('* stack:');
- e(_indent(err.stack));
- e('```');
- process.exit(1);
- });
-
-
- function main(argv) {
- try {
- var opts = parseArgv(argv);
- } catch (e) {
- warn('bunyan: error: %s', e.message);
- return drainStdoutAndExit(1);
- }
- if (opts.help) {
- printHelp();
- return;
- }
- if (opts.version) {
- console.log('bunyan ' + getVersion());
- return;
- }
- if (opts.pids && opts.args.length > 0) {
- warn('bunyan: error: can\'t use both "-p PID" (%s) and file (%s) args',
- opts.pids, opts.args.join(' '));
- return drainStdoutAndExit(1);
- }
- if (opts.color === null) {
- if (process.env.BUNYAN_NO_COLOR &&
- process.env.BUNYAN_NO_COLOR.length > 0) {
- opts.color = false;
- } else {
- opts.color = process.stdout.isTTY;
- }
- }
- usingAnsiCodes = opts.color; // intentionally global
- var stylize = (opts.color ? stylizeWithColor : stylizeWithoutColor);
-
- // Pager.
- var paginate = (
- process.stdout.isTTY &&
- process.stdin.isTTY &&
- !opts.pids && // Don't page if following process output.
- opts.args.length > 0 && // Don't page if no file args to process.
- process.platform !== 'win32' &&
- (nodeVer[0] > 0 || nodeVer[1] >= 8) &&
- (opts.paginate === true ||
- (opts.paginate !== false &&
- (!process.env.BUNYAN_NO_PAGER ||
- process.env.BUNYAN_NO_PAGER.length === 0))));
- if (paginate) {
- var pagerCmd = process.env.PAGER || 'less';
- /* JSSTYLED */
- assert.ok(pagerCmd.indexOf('"') === -1 && pagerCmd.indexOf("'") === -1,
- 'cannot parse PAGER quotes yet');
- var argv = pagerCmd.split(/\s+/g);
- var env = objCopy(process.env);
- if (env.LESS === undefined) {
- // git's default is LESS=FRSX. I don't like the 'S' here because
- // lines are *typically* wide with bunyan output and scrolling
- // horizontally is a royal pain. Note a bug in Mac's `less -F`,
- // such that SIGWINCH can kill it. If that rears too much then
- // I'll remove 'F' from here.
- env.LESS = 'FRX';
- }
- if (_DEBUG) warn('(pager: argv=%j, env.LESS=%j)', argv, env.LESS);
- // `pager` and `stdout` intentionally global.
- pager = spawn(argv[0], argv.slice(1),
- // Share the stderr handle to have error output come
- // straight through. Only supported in v0.8+.
- {env: env, stdio: ['pipe', 1, 2]});
- stdout = pager.stdin;
-
- // Early termination of the pager: just stop.
- pager.on('exit', function (pagerCode) {
- if (_DEBUG) warn('(bunyan: pager exit)');
- pager = null;
- stdout.end()
- stdout = process.stdout;
- cleanupAndExit(pagerCode);
- });
- }
-
- // Stdout error handling. (Couldn't setup until `stdout` was determined.)
- stdout.on('error', function (err) {
- if (_DEBUG) warn('(stdout error event: %s)', err);
- if (err.code === 'EPIPE') {
- drainStdoutAndExit(0);
- } else if (err.toString() === 'Error: This socket is closed.') {
- // Could get this if the pager closes its stdin, but hasn't
- // exited yet.
- drainStdoutAndExit(1);
- } else {
- warn(err);
- drainStdoutAndExit(1);
- }
- });
-
- var retval = 0;
- if (opts.pids) {
- processPids(opts, stylize, function (code) {
- cleanupAndExit(code);
- });
- } else if (opts.args.length > 0) {
- var files = opts.args;
- files.forEach(function (file) {
- streams[file] = { stream: null, records: [], done: false }
- });
- asyncForEach(files,
- function (file, next) {
- processFile(file, opts, stylize, function (err) {
- if (err) {
- warn('bunyan: %s', err.message);
- retval += 1;
- }
- next();
- });
- },
- function (err) {
- if (err) {
- warn('bunyan: unexpected error: %s', err.stack || err);
- return drainStdoutAndExit(1);
- }
- cleanupAndExit(retval);
- }
- );
- } else {
- processStdin(opts, stylize, function () {
- cleanupAndExit(retval);
- });
- }
- }
-
- if (require.main === module) {
- // HACK guard for <https://github.com/trentm/json/issues/24>.
- // We override the `process.stdout.end` guard that core node.js puts in
- // place. The real fix is that `.end()` shouldn't be called on stdout
- // in node core. Node v0.6.9 fixes that. Only guard for v0.6.0..v0.6.8.
- if ([0, 6, 0] <= nodeVer && nodeVer <= [0, 6, 8]) {
- var stdout = process.stdout;
- stdout.end = stdout.destroy = stdout.destroySoon = function () {
- /* pass */
- };
- }
-
- main(process.argv);
- }
|