From aa166b2f4678eb88503e632859fbfd0a00711a98 Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Sun, 29 Jan 2012 22:26:47 -0800 Subject: [PATCH] first stab. 'log.info(...)' --- README.md | 47 +++++++++++++ TODO.md | 10 +++ hi.js | 51 ++++++++++++++ lib/bunyan.js | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 6 ++ 5 files changed, 302 insertions(+) create mode 100644 README.md create mode 100644 TODO.md create mode 100644 hi.js create mode 100644 lib/bunyan.js create mode 100644 package.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..2677b4d --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +Bunyan -- a JSON Logger for node.js servers. + +Server logs should be structured. JSON's a good format. Let's do that: a log +record is one line of `JSON.stringify`'d output. Let's also specify some common +names for the requisite and common fields for a log record (see below). + +Also: log4j is way more than you need. + + +# Usage + + // hi.js + var Logger = require('bunyan'); + var log = new Logger({facility: "myapp", level: "info"}); + log.info("hi"); + + $ node hi.js + {"time":"2012-01-30T00:56:25.842Z","facility":"myapp","level":2,"message":"hi"} + + $ node hi.js | bunyan # CLI tool to filter/pretty-print JSON logs. + { + "time": "2012-01-30T00:56:25.842Z", + "facility": "myapp", + "level": 2, + "message": "hi" + } + + +# Levels + + fatal + error + warn + info + debug + +TODO + + +# Log Record Fields + +TODO + +# License + +MIT. + diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..feb73c0 --- /dev/null +++ b/TODO.md @@ -0,0 +1,10 @@ +- node-bunyan and push to priv +- .bind or equiv with diff name +- info's siblings +- `bunyan` cli +- expand set of fields +- renderer support (i.e. repr of a restify request obj) +- docs +- feel out usage +- Logger.set to mutate config or `this.fields` +- Logger.del to remove a field diff --git a/hi.js b/hi.js new file mode 100644 index 0000000..b4de68e --- /dev/null +++ b/hi.js @@ -0,0 +1,51 @@ +var Logger = require('./lib/bunyan'); +var log = new Logger({facility: "myapp", level: "info"}); +console.log("log.info() is:", log.info()) +log.info("hi"); +log.info("hi", "trent"); +log.info("hi %s there", true); +log.info({foo:"bar"}, "hi %d", 1, "two", 3); + + +console.log("\n--\n") + + +//console.log("\n--\n") +//xxx = console.log; +//var INFO = 2; +// +//function foo() { +// console.log("function foo arguments:", arguments) +//} +// +//function Bar() { +// this.level = 1; +//} +//Bar.prototype.info = function (fields, msg) { +// console.log("function info arguments:", arguments) +// var msgArgs; +// if (arguments.length === 0) { // `log.info()` +// return (this.level <= INFO); +// } else if (this.level > INFO) { +// return; +// } else if (typeof fields === 'string') { // `log.info(msg, ...)` +// fields = null; +// msgArgs = Array.prototype.slice.call(arguments); +// xxx("msgArgs: ", msgArgs, arguments) +// } else { // `log.info(fields, msg, ...)` +// msgArgs = Array.prototype.slice.call(arguments, 1); +// } +// xxx("info start: arguments:", arguments.length, arguments, msgArgs) +// //var rec = this._mkRecord(fields, INFO, msgArgs); +// //this._emit(rec); +//} +// +// +//foo() +//foo("one") +//foo("one", "two") +// +//bar = new Bar(); +//bar.info() +//bar.info("one") +//bar.info("one", "two") diff --git a/lib/bunyan.js b/lib/bunyan.js new file mode 100644 index 0000000..fabff4f --- /dev/null +++ b/lib/bunyan.js @@ -0,0 +1,188 @@ +/* + * Copyright 2012 (c) Trent Mick. All rights reserved. + */ + +// Bunyan log format version. This becomes the 'v' field on all log records. +// `0` is until I release a version "1.0.0" of node-bunyan. Thereafter, +// starting with `1`, this will be incremented if there is any backward +// incompatible change to the log record format. Details will be in +// "CHANGES.md" (the change log). +var VERSION = 0; + +var paul = function paul(s) { // internal dev/debug logging + var args = ["PAUL: "+s].concat(Array.prototype.slice.call(arguments, 1)); + console.error.apply(this, args); +}; +var paul = function paul() {}; // uncomment to turn of debug logging + +var fs = require('fs'); +var util = require('util'); + + + +//---- Internal support stuff + +function objCopy(obj) { + var copy = {}; + Object.keys(obj).forEach(function (k) { + copy[k] = obj[k]; + }); + return copy; +} + +var format = util.format; +if (!format) { + // If not node 0.6, then use its `util.format`: + // : + 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; + }; +} + + + +//---- Levels + +var DEBUG = 1; +var INFO = 2; +var WARN = 3; +var ERROR = 4; +var FATAL = 5; + +var levelFromName = { + 'debug': DEBUG, + 'info': INFO, + 'warn': WARN, + 'error': ERROR, + 'fatal': FATAL +}; +var nameFromLevel = { + DEBUG: 'debug', + INFO: 'info', + WARN: 'warn', + ERROR: 'error', + FATAL: 'fatal' +}; + + +//---- Logger class + +function Logger(options) { + paul('Logger start:', options) + if (! this instanceof Logger) { + return new Logger(options); + } + + // These are the default fields for log records (minus the attributes + // removed in this constructor). To allow storing raw log records + // (unrendered), `this.fields` must never be mutated. Create a copy, if + // necessary. + this.fields = objCopy(options); + + if (options.stream) { + this.stream = options.stream; + delete this.fields.stream; + } else { + this.stream = process.stdout; + } + if (options.level) { + this.level = (typeof(options.level) === 'string' + ? levelFromName[options.level] + : options.level); + if (! (DEBUG <= this.level && this.level <= FATAL)) { + throw new Error('invalid level: ' + options.level); + } + delete this.fields.level; + } else { + this.level = 2; // INFO is default level. + } + paul('Logger default fields:', this.fields); +} + + +/** + * A log record is a 4-tuple: + * [, + * , + * , + * ] + * For Perf reasons, we only render this down to a single object when + * it is emitted. + */ +Logger.prototype._mkRecord = function (fields, level, msgArgs) { + var recFields = (fields ? objCopy(fields) : null); + return [this.fields, recFields, level, msgArgs]; +} + +Logger.prototype._emit = function (rec) { + var obj = objCopy(rec[0]); + var recFields = rec[1]; + if (recFields) { + Object.keys(recFields).forEach(function (k) { + obj[k] = recFields[k]; + }); + } + obj.level = rec[2]; + paul("Record:", rec) + obj.msg = format.apply(this, rec[3]); + obj.v = VERSION; + this.stream.write(JSON.stringify(obj) + '\n'); +} + +/** + * Usages: + * log.info( fields, msg, ...) + * log.info( msg, ...) + * log.info() -> boolean is-info-enabled + */ +Logger.prototype.info = function () { + var fields = null, msgArgs = null; + if (arguments.length === 0) { // `log.info()` + return (this.level <= INFO); + } else if (this.level > INFO) { + return; + } else if (typeof arguments[0] === 'string') { // `log.info(msg, ...)` + fields = null; + msgArgs = Array.prototype.slice.call(arguments); + } else { // `log.info(fields, msg, ...)` + fields = arguments[0]; + msgArgs = Array.prototype.slice.call(arguments, 1); + } + var rec = this._mkRecord(fields, INFO, msgArgs); + this._emit(rec); +} + +module.exports = Logger; + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..b7ab8d9 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "name": "bunyan", + "version": "1.0.0", + "private": true, + "main": "./lib/bunyan.js" +}