diff --git a/bin/bunyan b/bin/bunyan index a14cf79..11045dd 100755 --- a/bin/bunyan +++ b/bin/bunyan @@ -144,6 +144,24 @@ function printHelp() { util.puts(" json-N: JSON output, N-space indent, e.g. 'json-4'"); util.puts(" inspect: node.js `util.inspect` output"); util.puts(" -j shortcut for `-o json`"); + util.puts(" -l, --level LEVEL"); + util.puts(" Only show messages at or above the specified level."); + util.puts(" (See 'Log Levels' below.)"); + util.puts(" -c, --condition CONDITION"); + util.puts(" Run each log message through the condition"); + util.puts(" and only show those that resolve to a truish value."); + util.puts(""); + util.puts("Log Levels:"); + util.puts(" Either numeric values or their associated strings are valid for the"); + util.puts(" -l --level argument. However, --condition scripts will see a numeric"); + util.puts(" 'level' value, not a string."); + util.puts(""); + Object.keys(levelFromName).forEach(function(name) { + var n = name; + while (n.length < 6) + n += " "; + console.log(" %s %d", n, levelFromName[name]); + }); util.puts(""); util.puts("See for more complete docs."); } @@ -176,6 +194,23 @@ function gotRecord(file, line, rec, opts, stylize) emitNextRecord(opts, stylize); } +function filterRecord(rec, opts) +{ + if (opts.level && rec.level < opts.level) { + return false; + } + + if (opts.conditions) { + for (var i = 0; i < opts.conditions.length; i++) { + var pass = opts.conditions[i].runInNewContext(rec); + if (!pass) + return false; + } + } + + return true; +} + function emitNextRecord(opts, stylize) { var ofile, ready, minfile, rec; @@ -252,13 +287,15 @@ function parseArgv(argv) { help: false, color: process.stdout.isTTY, outputMode: OM_PAUL, - jsonIndent: 2 + jsonIndent: 2, + level: -Infinity, + conditions: null }; // 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}; + var optTakesArg = {'d': true, 'o': true, 'c': true, 'l': 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(""); @@ -317,6 +354,26 @@ function parseArgv(argv) { case "-j": // output with JSON.stringify parsed.outputMode = OM_JSON; 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": + var condition = args.shift(); + parsed.conditions = parsed.conditions || [] + var scriptName = 'bunyan-condition-'+parsed.conditions.length; + var script = vm.createScript(condition, scriptName); + parsed.conditions.push(script); + break default: // arguments if (!endOfOptions && arg.length > 0 && arg[0] === '-') { throw new Error("unknown option '"+arg+"'"); @@ -411,7 +468,13 @@ function handleLogLine(file, line, opts, stylize) { } } - if (file === null || !isValidRecord(rec)) + if (!isValidRecord(rec)) + return emitRecord(rec, line, opts, stylize); + + if (!filterRecord(rec, opts)) + return; + + if (file === null) return emitRecord(rec, line, opts, stylize); return gotRecord(file, line, rec, opts, stylize); diff --git a/package.json b/package.json index d47241e..eab8958 100644 --- a/package.json +++ b/package.json @@ -18,5 +18,9 @@ "devDependencies": { "tap": "0.2.0", "ben": "0.0.0" + }, + + "scripts": { + "test": "tap test/*.js" } } diff --git a/test/cli.test.js b/test/cli.test.js index 1f29db9..2a7ac09 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -200,3 +200,77 @@ test('mixed text and gzip logs', function (t) { t.end(); }); }); + +test('--level 40', function (t) { + expect = [ + '# levels\n', + '[2012-02-08T22:56:53.856Z] WARN: myservice/123 on example.com: My message\n', + '[2012-02-08T22:56:54.856Z] ERROR: myservice/123 on example.com: My message\n', + '[2012-02-08T22:56:55.856Z] LVL55: myservice/123 on example.com: My message\n', + '[2012-02-08T22:56:56.856Z] FATAL: myservice/123 on example.com: My message\n', + '\n', + '# extra fields\n', + '\n', + '# bogus\n', + 'not a JSON line\n', + '{"hi": "there"}\n' + ].join(''); + exec(BUNYAN + ' -l 40 corpus/all.log', function (err, stdout, stderr) { + t.error(err); + t.equal(stdout, expect); + t.end(); + exec(BUNYAN + ' --level 40 corpus/all.log', function (err, stdout, stderr) { + t.error(err); + t.equal(stdout, expect); + t.end(); + }); + }); +}); + +test('--condition "level === 10 && pid === 123"', function (t) { + var expect = [ + '# levels\n', + '[2012-02-08T22:56:50.856Z] TRACE: myservice/123 on example.com: My message\n', + '\n', + '# extra fields\n', + '\n', + '# bogus\n', + 'not a JSON line\n', + '{"hi": "there"}\n' + ].join(''); + exec(BUNYAN + ' -c "level === 10 && pid === 123" corpus/all.log', + function (err, stdout, stderr) { + t.error(err); + t.equal(stdout, expect); + t.end(); + exec(BUNYAN + ' --condition "level === 10 && pid === 123" corpus/all.log', + function (err, stdout, stderr) { + t.error(err); + t.equal(stdout, expect); + t.end(); + }); + }); +}); + +// multiple +// not sure if this is a bug or a feature. let's call it a feature! +test('multiple --conditions', function (t) { + var expect = [ + '# levels\n', + '[2012-02-08T22:56:53.856Z] WARN: myservice/1 on example.com: My message\n', + '\n', + '# extra fields\n', + '\n', + '# bogus\n', + 'not a JSON line\n', + '{"hi": "there"}\n' + ].join(''); + t.end(); + exec(BUNYAN + ' test/corpus/all.log ' + + '-c "if (level === 40) pid = 1; true" ' + + '-c "pid === 1"', function (err, stdout, stderr) { + t.error(err); + t.equal(stdout, expect); + t.end(); + }); +});