Permalink
Browse files

CLI file args: bunyan foo.log bar.log

  • Loading branch information...
1 parent 77b99b6 commit 9feb9c82bdfc0143a85c3cf6813d0ba9bdb01cc8 @trentm committed Feb 24, 2012
Showing with 320 additions and 44 deletions.
  1. +6 −1 CHANGES.md
  2. +16 −14 README.md
  3. +168 −29 bin/bunyan
  4. +130 −0 test/cli.test.js
View
@@ -2,7 +2,12 @@
## bunyan 0.6.6 (not yet released)
-(nothing yet)
+- Add support to the `bunyan` CLI taking log file path args, `bunyan foo.log`,
+ in addition to the usual `cat foo.log | bunyan`.
+- Improve reliability of the default output formatting of the `bunyan` CLI.
+ Before it could blow up processing log records missing some expected
+ fields.
+
## bunyan 0.6.5
View
@@ -118,7 +118,7 @@ same config as its parent, plus include the "component" field.
wuzzle.woos();
log.info("done with the wuzzle")
-* * *
+### A little log.child hack.
An example and a hack: The [node-restify](https://github.com/mcavage/node-restify)
framework integrates bunyan. One feature is that each restify request handler
@@ -129,12 +129,12 @@ includes a `req.log` logger that is:
Apps using restify can then use `req.log` and have all such log records
include the unique request id (as "req_id"). Handy.
-*What is that `true`?* It is a small bunyan hack by which you can assert that
-you're just adding simple fields to the child logger. This makes `log.child`
-10x faster and, hence, never a worry for slowing down HTTP request handling.
-See the changelog for node-bunyan 0.3.0 for details.
+The hack is that `, true`. It is a small bunyan performance hack by which you
+can assert that you're just adding simple fields to the child logger -- i.e.
+not special fields like "level", "serializers", "streams". This makes
+`log.child` 10x faster and, hence, never a worry for slowing down HTTP
+request handling. See the changelog for node-bunyan 0.3.0 for details.
-* * *
## serializers
@@ -181,8 +181,8 @@ in your log record will be replaced with a short error message.*
## src
-The **call source file, line and function** (if not at the global level) can
-be added to log records by using the `src: true` config option:
+The **source file, line and function of the log call site** can be added to
+log records by using the `src: true` config option:
var log = new Logger({src: true, ...});
@@ -209,20 +209,22 @@ in production.**
# Levels
+The log levels in bunyan are:
+
- "fatal" (60): the service/app is going to stop or become unusable now
- "error" (50): fatal for a particular request, but the service/app continues servicing other requests
- "warn" (40): a note on something that should probably be looked at by an operator
- "info" (30): detail on regular operation
- "debug" (20): anything else, i.e. too verbose to be included in "info" level.
- "trace" (10): logging from external libraries used by your app
-"debug" should be used sparingly. Information that will be useful to debug
-errors *post mortem* should usually be included in "info" messages if it's
-generally relevant or else with the corresponding "error" event. Don't rely on
-spewing mostly irrelevant debug messages all the time and sifting through them
-when an error occurs.
+General level usage suggestions: "debug" should be used sparingly.
+Information that will be useful to debug errors *post mortem* should usually
+be included in "info" messages if it's generally relevant or else with the
+corresponding "error" event. Don't rely on spewing mostly irrelevant debug
+messages all the time and sifting through them when an error occurs.
-Integers are used for the actual level values (1 for "trace", ..., 6 for
+Integers are used for the actual level values (10 for "trace", ..., 60 for
"fatal") and constants are defined for the (Logger.TRACE ... Logger.DEBUG).
The lowercase level names are aliases supported in the API.
View
@@ -11,6 +11,7 @@ var util = require('util');
var pathlib = require('path');
var vm = require('vm');
var http = require('http');
+var fs = require('fs');
var warn = console.warn;
@@ -126,7 +127,7 @@ function objCopy(obj) {
function printHelp() {
util.puts("Usage:");
- util.puts(" cat some-json-log-file.log | bunyan [OPTIONS]");
+ util.puts(" bunyan [OPTIONS] [FILE ...]");
util.puts("");
util.puts("Pipe in your JSON log file content (e.g. bunyan logs) for nicer ");
util.puts("output");
@@ -146,7 +147,7 @@ function printHelp() {
util.puts(" inspect: node.js `util.inspect` output");
util.puts(" -j shortcut for `-o json`");
util.puts("");
- util.puts("See <https://github.com/trentm/json> for more complete docs.");
+ util.puts("See <https://github.com/trentm/node-bunyan> for more complete docs.");
}
@@ -273,6 +274,8 @@ var colors = {
};
function stylizeWithColor(str, color) {
+ if (!str)
+ return '';
var codes = colors[color];
if (codes) {
return '\033[' + codes[0] + 'm' + str +
@@ -288,6 +291,25 @@ function stylizeWithoutColor(str, color) {
/**
+ * Is this a valid Bunyna 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;
+ }
+}
+
+
+/**
* Print out a single result, considering input options.
*/
function handleLogLine(line, opts, stylize) {
@@ -316,6 +338,10 @@ function handleLogLine(line, opts, stylize) {
// 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;
var time = stylize('[' + rec.time + ']', 'XXX');
@@ -477,7 +503,10 @@ function handleLogLine(line, opts, stylize) {
case OM_SIMPLE:
// <http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/SimpleLayout.html>
- emit(format("%s - %s", upperNameFromLevel[rec.level] || "LVL" + rec.level,
+ if (!isValidRecord(rec)) {
+ return emit(line + '\n');
+ }
+ emit(format("%s - %s\n", upperNameFromLevel[rec.level] || "LVL" + rec.level,
rec.msg));
break;
default:
@@ -527,30 +556,17 @@ function drainStdoutAndExit(code) {
}
-
-//---- mainline
-
-function main(argv) {
- var opts;
- try {
- opts = parseArgv(argv);
- } catch (e) {
- warn("json: error: %s", e.message)
- return drainStdoutAndExit(1);
- }
- //warn(opts);
- if (opts.help) {
- printHelp();
- return;
- }
- if (opts.version) {
- util.puts("bunyan " + getVersion());
- return;
- }
- var stylize = (opts.color ? stylizeWithColor : stylizeWithoutColor);
-
+/**
+ * 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.openStdin();
+ var stdin = process.stdin;
+ stdin.resume();
stdin.setEncoding('utf8');
stdin.on('data', function (chunk) {
var lines = chunk.split(/\r\n|\n/);
@@ -569,21 +585,144 @@ function main(argv) {
handleLogLine(lines[i], opts, stylize);
}
});
-
stdin.on('end', function () {
if (leftover) {
handleLogLine(leftover, opts, stylize);
leftover = '';
}
+ callback();
});
}
+
+/**
+ * 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, {encoding: 'utf8'});
+
+ stream.on('error', function (err) {
+ callback(err);
+ })
+
+ var leftover = ''; // Left-over partial line from last chunk.
+ stream.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(leftover + lines[0], opts, stylize);
+ }
+ leftover = lines.pop();
+ length -= 1;
+ for (var i=1; i < length; i++) {
+ handleLogLine(lines[i], opts, stylize);
+ }
+ });
+
+ stream.on('end', function () {
+ if (leftover) {
+ handleLogLine(leftover, opts, stylize);
+ leftover = '';
+ }
+ callback();
+ });
+}
+
+
+/**
+ * From node async module.
+ */
+function asyncForEachSeries(arr, iterator, callback) {
+ if (!arr.length) {
+ return callback();
+ }
+ var completed = 0;
+ var iterate = function () {
+ iterator(arr[completed], function (err) {
+ if (err) {
+ callback(err);
+ callback = function () {};
+ }
+ else {
+ completed += 1;
+ if (completed === arr.length) {
+ callback();
+ }
+ else {
+ iterate();
+ }
+ }
+ });
+ };
+ iterate();
+};
+
+
+
+//---- mainline
+
+function main(argv) {
+ var opts;
+ try {
+ opts = parseArgv(argv);
+ } catch (e) {
+ warn("bunyan: error: %s", e.message)
+ return drainStdoutAndExit(1);
+ }
+ if (opts.help) {
+ printHelp();
+ return;
+ }
+ if (opts.version) {
+ util.puts("bunyan " + getVersion());
+ return;
+ }
+ var stylize = (opts.color ? stylizeWithColor : stylizeWithoutColor);
+
+ var retval = 0;
+ if (opts.args.length > 0) {
+ var files = opts.args;
+ var i = 0;
+ asyncForEachSeries(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);
+ }
+ process.exit(retval);
+ }
+ );
+ } else {
+ processStdin(opts, stylize, function () {
+ process.exit(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. Hopefully node v0.6.9 will fix that. Only guard
- // for v0.6.0..v0.6.8.
+ // in node core. Node v0.6.9 fixes that. Only guard for v0.6.0..v0.6.8.
var nodeVer = process.versions.node.split('.').map(Number);
if ([0,6,0] <= nodeVer && nodeVer <= [0,6,8]) {
var stdout = process.stdout;
Oops, something went wrong.

0 comments on commit 9feb9c8

Please sign in to comment.