diff --git a/README.md b/README.md index b8083b7..14c794b 100644 --- a/README.md +++ b/README.md @@ -459,6 +459,36 @@ Supported stream types are: // Handle stream write or create error here. }); +Bunyan comes with a special stream called a RingBuffer which keeps the last N +records in memory and does *not* write the data anywhere else. One common +strategy is to log 'info' and higher to a normal log file but log all records +(including 'trace') to a ringbuffer that you can access via a debugger, or your +own HTTP interface, or a post-mortem facility like MDB or node-panic. + +To use a RingBuffer: + + /* Create a ring buffer that stores the last 100 entries. */ + var bunyan = require('bunyan'); + var ringbuffer = new bunyan.RingBuffer({ limit: 100 }); + var log = new bunyan({ + name: "foo", + stream: ringbuffer, + level: "debug" + }); + + log.info('hello world'); + console.log(ringbuffer.entries); + +This example emits: + + [ { name: 'foo', + hostname: '912d2b29', + pid: 50346, + level: 30, + msg: 'hello world', + time: '2012-06-19T21:34:19.906Z', + v: 0 } ] + # License diff --git a/TODO.md b/TODO.md index 58d598f..4292142 100644 --- a/TODO.md +++ b/TODO.md @@ -15,7 +15,6 @@ works *and* that an existing field in the parent is not *re-serialized*. - a "rolling-file" stream: but specifically by time, e.g. hourly. (MarkC requested) -- ringBuffer stream - split out `bunyan` cli to a "bunyan" or "bunyan-reader" or "node-bunyan-reader" as the basis for tools to consume bunyan logs. It can grow indep of node-bunyan for generating the logs. diff --git a/examples/ringbuffer.js b/examples/ringbuffer.js new file mode 100644 index 0000000..06d4cb3 --- /dev/null +++ b/examples/ringbuffer.js @@ -0,0 +1,11 @@ +/* Create a ring buffer that stores the last 100 entries. */ +var bunyan = require('..'); +var ringbuffer = new bunyan.RingBuffer({ limit: 100 }); +var log = new bunyan({ + name: 'foo', + stream: ringbuffer, + level: 'debug' +}); + +log.info('hello world'); +console.log(ringbuffer.entries); diff --git a/lib/bunyan.js b/lib/bunyan.js index aa294f3..52b5f99 100644 --- a/lib/bunyan.js +++ b/lib/bunyan.js @@ -954,6 +954,58 @@ var errSerializer = Logger.stdSerializers.err = function err(err) { }; +/** + * RingBuffer is a Writable Stream that just stores the last N entries in + * memory. + * + * @param options {Object}, with the following fields: + * + * - limit: number of entries to keep in memory + */ +function RingBuffer(options) { + this.limit = options && options.limit ? options.limit : 100; + this.writable = true; + this.entries = []; + EventEmitter.call(this); +} + +util.inherits(RingBuffer, EventEmitter); + +RingBuffer.prototype.write = function (str) { + var json; + + if (!this.writable) + throw (new Error('RingBuffer has been ended already')); + + try { + json = JSON.parse(str); + this.entries.push(json); + } catch (ex) { + this.entries.push(str); + } + + if (this.entries.length > this.limit) + this.entries.shift(); + + return (true); +}; + +RingBuffer.prototype.end = function () { + if (arguments.length > 0) + this.write.apply(this, Array.prototype.slice.call(arguments)); + this.writable = false; +}; + +RingBuffer.prototype.destroy = function () { + this.writable = false; + this.emit('close'); +}; + +RingBuffer.prototype.destroySoon = function () { + this.destroy(); + this.emit('close'); +}; + //---- Exports @@ -972,3 +1024,5 @@ module.exports.LOG_VERSION = LOG_VERSION; module.exports.createLogger = function createLogger(options) { return new Logger(options); }; + +module.exports.RingBuffer = RingBuffer; diff --git a/test/ringbuffer.test.js b/test/ringbuffer.test.js new file mode 100644 index 0000000..8a0329e --- /dev/null +++ b/test/ringbuffer.test.js @@ -0,0 +1,38 @@ +/* + * Test the RingBuffer output stream. + */ + +var test = require('tap').test; +var Logger = require('../lib/bunyan'); +var ringbuffer = new Logger.RingBuffer({ 'limit': 5 }); + +var log1 = new Logger({ + name: 'log1', + streams: [ + { + stream: ringbuffer, + level: 'info' + } + ] +}); + +test('ringbuffer', function (t) { + log1.info('hello'); + log1.trace('there'); + log1.error('chucklebucket'); + t.equal(ringbuffer.entries.length, 2); + t.equal(ringbuffer.entries[0]['msg'], 'hello'); + t.equal(ringbuffer.entries[1]['msg'], 'chucklebucket'); + log1.error('one'); + log1.error('two'); + log1.error('three'); + t.equal(ringbuffer.entries.length, 5); + log1.error('four'); + t.equal(ringbuffer.entries.length, 5); + t.equal(ringbuffer.entries[0]['msg'], 'chucklebucket'); + t.equal(ringbuffer.entries[1]['msg'], 'one'); + t.equal(ringbuffer.entries[2]['msg'], 'two'); + t.equal(ringbuffer.entries[3]['msg'], 'three'); + t.equal(ringbuffer.entries[4]['msg'], 'four'); + t.end(); +});