diff --git a/CHANGES.md b/CHANGES.md index 1068d65..9149843 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,16 @@ ## bunyan 0.14.6 (not yet released) +- [issue #49] Allow a `log.child()` to specify the level of inherited streams. + For example: + + # Before + var childLog = log.child({...}); + childLog.level('debug'); + + # After + var childLog = log.child({..., level: 'debug'}); + - Improve the crash message to make it easier to provide relevant details in a bug report. diff --git a/lib/bunyan.js b/lib/bunyan.js index b62b9e9..f2cfd81 100644 --- a/lib/bunyan.js +++ b/lib/bunyan.js @@ -238,9 +238,8 @@ function Logger(options, _childOptions, _childSimple) { throw new TypeError('invalid options.name: child cannot set logger name'); } } - if ((options.stream || options.level) && options.streams) { - throw new TypeError( - 'cannot mix "streams" with "stream" or "level" options'); + if (options.stream && options.streams) { + throw new TypeError('cannot mix "streams" and "stream" options'); } if (options.streams && !Array.isArray(options.streams)) { throw new TypeError('invalid options.streams: must be an array') @@ -289,6 +288,9 @@ function Logger(options, _childOptions, _childSimple) { this.serializers = objCopy(parent.serializers); this.src = parent.src; this.fields = objCopy(parent.fields); + if (options.level) { + this.level(options.level); + } } else { this._level = Number.POSITIVE_INFINITY; this.streams = []; @@ -314,6 +316,8 @@ function Logger(options, _childOptions, _childSimple) { if (s.level) { s.level = resolveLevel(s.level); + } else if (options.level) { + s.level = resolveLevel(options.level); } else { s.level = INFO; } @@ -370,7 +374,8 @@ function Logger(options, _childOptions, _childSimple) { }); } - // Handle *config* options. + // Handle *config* options (i.e. options that are not just plain data + // for log records). if (options.stream) { addStream({ type: 'stream', @@ -380,6 +385,8 @@ function Logger(options, _childOptions, _childSimple) { }); } else if (options.streams) { options.streams.forEach(addStream); + } else if (parent && options.level) { + this.level(options.level); } else if (!parent) { addStream({ type: 'stream', @@ -439,9 +446,12 @@ util.inherits(Logger, EventEmitter); * @param options {Object} Optional. Set of options to apply to the child. * All of the same options for a new Logger apply here. Notes: * - The parent's streams are inherited and cannot be removed in this - * call. + * call. Any given `streams` are *added* to the set inherited from + * the parent. * - The parent's serializers are inherited, though can effectively be * overwritten by using duplicate keys. + * - Can use `level` to set the level of the streams inherited from + * the parent. The level for the parent is NOT affected. * @param simple {Boolean} Optional. Set to true to assert that `options` * (a) only add fields (no config) and (b) no serialization handling is * required for them. IOW, this is a fast path for frequent child diff --git a/test/child-behaviour.test.js b/test/child-behaviour.test.js new file mode 100644 index 0000000..a59e8ad --- /dev/null +++ b/test/child-behaviour.test.js @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2012 Trent Mick. All rights reserved. + * + * Test some `.child(...)` behaviour. + */ + +var test = require('tap').test; +var bunyan = require('../lib/bunyan'); + +function CapturingStream(recs) { + this.recs = recs || []; +} +CapturingStream.prototype.write = function (rec) { + this.recs.push(rec); +} + + + +test('child can add stream', function (t) { + var dadStream = new CapturingStream(); + var dad = bunyan.createLogger({ + name: 'surname', + streams: [{ + type: 'raw', + stream: dadStream, + level: 'info' + }] + }); + + var sonStream = new CapturingStream(); + var son = dad.child({ + component: 'son', + streams: [{ + type: 'raw', + stream: sonStream, + level: 'debug' + }] + }); + + dad.info('info from dad'); + dad.debug('debug from dad'); + son.debug('debug from son'); + + var rec; + t.equal(dadStream.recs.length, 1); + rec = dadStream.recs[0]; + t.equal(rec.msg, 'info from dad'); + t.equal(sonStream.recs.length, 1); + rec = sonStream.recs[0]; + t.equal(rec.msg, 'debug from son'); + + t.end(); +}); + + +test('child can set level of inherited streams', function (t) { + var dadStream = new CapturingStream(); + var dad = bunyan.createLogger({ + name: 'surname', + streams: [{ + type: 'raw', + stream: dadStream, + level: 'info' + }] + }); + + // Intention here is that the inherited `dadStream` logs at 'debug' level + // for the son. + var son = dad.child({ + component: 'son', + level: 'debug' + }); + + dad.info('info from dad'); + dad.debug('debug from dad'); + son.debug('debug from son'); + + var rec; + t.equal(dadStream.recs.length, 2); + rec = dadStream.recs[0]; + t.equal(rec.msg, 'info from dad'); + rec = dadStream.recs[1]; + t.equal(rec.msg, 'debug from son'); + + t.end(); +}); + + +test('child can set level of inherited streams and add streams', function (t) { + var dadStream = new CapturingStream(); + var dad = bunyan.createLogger({ + name: 'surname', + streams: [{ + type: 'raw', + stream: dadStream, + level: 'info' + }] + }); + + // Intention here is that the inherited `dadStream` logs at 'debug' level + // for the son. + var sonStream = new CapturingStream(); + var son = dad.child({ + component: 'son', + level: 'trace', + streams: [{ + type: 'raw', + stream: sonStream, + level: 'debug' + }] + }); + + dad.info('info from dad'); + dad.trace('trace from dad'); + son.trace('trace from son'); + son.debug('debug from son'); + + t.equal(dadStream.recs.length, 3); + t.equal(dadStream.recs[0].msg, 'info from dad'); + t.equal(dadStream.recs[1].msg, 'trace from son'); + t.equal(dadStream.recs[2].msg, 'debug from son'); + + t.equal(sonStream.recs.length, 1); + t.equal(sonStream.recs[0].msg, 'debug from son'); + + t.end(); +}); diff --git a/test/ctor.test.js b/test/ctor.test.js index 9db59be..a5c0bcc 100644 --- a/test/ctor.test.js +++ b/test/ctor.test.js @@ -26,10 +26,6 @@ test('ensure Logger creation options', function (t) { t.throws(function () { new Logger(options); }, 'cannot use "stream" and "streams"'); - options = {name: 'foo', level: 'info', streams: []}; - t.throws(function () { new Logger(options); }, - 'cannot use "level" and "streams"'); - // https://github.com/trentm/node-bunyan/issues/3 options = {name: 'foo', streams: {}}; t.throws(function () { new Logger(options); }, @@ -72,10 +68,6 @@ test('ensure Logger creation options (createLogger)', function (t) { t.throws(function () { bunyan.createLogger(options); }, 'cannot use "stream" and "streams"'); - options = {name: 'foo', level: 'info', streams: []}; - t.throws(function () { bunyan.createLogger(options); }, - 'cannot use "level" and "streams"'); - // https://github.com/trentm/node-bunyan/issues/3 options = {name: 'foo', streams: {}}; t.throws(function () { bunyan.createLogger(options); }, @@ -122,10 +114,6 @@ test('ensure Logger child() options', function (t) { t.throws(function () { log.child(options); }, 'cannot use "stream" and "streams"'); - options = {level: 'info', streams: []}; - t.throws(function () { log.child(options); }, - 'cannot use "level" and "streams"'); - // https://github.com/trentm/node-bunyan/issues/3 options = {streams: {}}; t.throws(function () { log.child(options); },