diff --git a/CHANGES.md b/CHANGES.md index 2bb44e7..961adcc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,9 @@ ## bunyan 0.5.0 (not yet released) +- Add `log.level(...)` and `log.levels(...)` API for changing logger stream + levels. +- Add `TRACE|DEBUG|INFO|WARN|ERROR|FATAL` level constants to exports. - Add `log.info(err)` special case for logging an `Error` instance. For example `log.info(new TypeError("boom")` will produce: diff --git a/README.md b/README.md index 3190a49..d1a25be 100644 --- a/README.md +++ b/README.md @@ -220,6 +220,22 @@ Integers are used for the actual level values (1 for "trace", ..., 6 for "fatal") and constants are defined for the (Logger.TRACE ... Logger.DEBUG). The lowercase level names are aliases supported in the API. +Here is the API for changing levels in an existing logger: + + log.level() -> INFO // gets current level (lowest level of all streams) + + log.level(INFO) // set all streams to level INFO + log.level("info") // set all streams to level INFO + + log.levels() -> [DEBUG, INFO] // get array of levels of all streams + log.levels(0) -> DEBUG // get level of stream at index 0 + log.levels("foo") // get level of stream with name "foo" + + log.levels(0, INFO) // set level of stream 0 to INFO + log.levels(0, "info") // can use "info" et al aliases + log.levels("foo", WARN) // set stream named "foo" to WARN + + # Log Record Fields diff --git a/TODO.md b/TODO.md index b924775..0adc196 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,8 @@ -- line/file: possible to get quickly with v8? Yunong asked. - file/line/func (?) - Logger.setLevel()? How to change level for a given stream. Default all, else, give an index... or type ... or support stream "names". Some positives to stream names. +- service -> name +- 10, 20,... - bunyan cli: more layouts (http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/EnhancedPatternLayout.html) Custom log formats (in config file? in '-f' arg) using printf or hogan.js or whatever. Dap wants field width control for lining up. Hogan.js is diff --git a/examples/level.js b/examples/level.js new file mode 100644 index 0000000..ea0ebce --- /dev/null +++ b/examples/level.js @@ -0,0 +1,47 @@ +// Play with setting levels. +// +// TODO: put this in a damn test suite + +var Logger = require('../lib/bunyan'), + DEBUG = Logger.DEBUG, + INFO = Logger.INFO, + WARN = Logger.WARN; +var assert = require('assert'); + +// Basic usage. +var log = new Logger({ + service: 'example-level', + streams: [ + { + name: 'stdout', + stream: process.stdout, + level: 'debug' + }, + { + name: 'stderr', + stream: process.stderr + } + ] +}); + +assert.equal(log.level(), DEBUG); +assert.equal(log.levels()[0], DEBUG); +assert.equal(log.levels()[1], INFO); +assert.equal(log.levels(0), DEBUG); +assert.equal(log.levels(1), INFO); + +assert.equal(log.levels('stdout'), DEBUG) +try { + log.levels('foo') +} catch (e) { + assert.ok(e.message.indexOf('name') !== -1) +} + +log.trace("no one should see this") +log.debug("should see this once (on stdout)") +log.info("should see this twice") +log.levels('stdout', INFO) +log.debug("no one should see this either") +log.level('trace') +log.trace('should see this twice as 4th and 5th emitted log messages') + diff --git a/lib/bunyan.js b/lib/bunyan.js index 02d56e2..aa4f108 100644 --- a/lib/bunyan.js +++ b/lib/bunyan.js @@ -22,6 +22,7 @@ var xxx = function xxx() {}; // uncomment to turn of debug logging var os = require('os'); var fs = require('fs'); var util = require('util'); +var assert = require('assert'); @@ -55,7 +56,7 @@ if (!format) { } return objects.join(' '); } - + var i = 1; var args = arguments; var len = args.length; @@ -178,7 +179,7 @@ function Logger(options, _childOptions, _childSimple) { if (! this instanceof Logger) { return new Logger(options, _childOptions); } - + // Input arg validation. var parent; if (_childOptions !== undefined) { @@ -194,14 +195,14 @@ function Logger(options, _childOptions, _childSimple) { if ((options.stream || options.level) && options.streams) { throw new TypeError('cannot mix "streams" with "stream" or "level" options'); } - + // Fast path for simple child creation. if (parent && _childSimple) { // Single to stream close handling that this child owns none of its // streams. this._isSimpleChild = true; - this.level = parent.level; + this._level = parent._level; this.streams = parent.streams; this.serializers = parent.serializers; this.src = parent.src; @@ -217,7 +218,7 @@ function Logger(options, _childOptions, _childSimple) { // Null values. var self = this; if (parent) { - this.level = parent.level; + this._level = parent._level; this.streams = []; for (var i = 0; i < parent.streams.length; i++) { var s = objCopy(parent.streams[i]); @@ -228,13 +229,13 @@ function Logger(options, _childOptions, _childSimple) { this.src = parent.src; this.fields = objCopy(parent.fields); } else { - this.level = Number.POSITIVE_INFINITY; + this._level = Number.POSITIVE_INFINITY; this.streams = []; this.serializers = null; this.src = false; this.fields = {}; } - + // Helpers function addStream(s) { s = objCopy(s); @@ -254,8 +255,8 @@ function Logger(options, _childOptions, _childSimple) { } else { s.level = INFO; } - if (s.level < self.level) { - self.level = s.level; + if (s.level < self._level) { + self._level = s.level; } switch (s.type) { @@ -324,7 +325,7 @@ function Logger(options, _childOptions, _childSimple) { this.src = true; } xxx("Logger: ", self) - + // Fields. // These are the default fields for log records (minus the attributes // removed in this constructor). To allow storing raw log records @@ -399,6 +400,98 @@ Logger.prototype.child = function (options, simple) { //} +/** + * Get/set the level of all streams on this logger. + * + * Get Usage: + * // Returns the current log level (lowest level of all its streams). + * log.level() -> INFO + * + * Set Usage: + * log.level(INFO) // set all streams to level INFO + * log.level("info") // can use "info" et al aliases + */ +Logger.prototype.level = function level(value) { + if (value === undefined) { + return this._level; + } + var newLevel = resolveLevel(value); + var len = this.streams.length; + for (var i = 0; i < len; i++) { + this.streams[i].level = newLevel; + } + this._level = newLevel; +} + + +/** + * Get/set the level of a particular stream on this logger. + * + * Get Usage: + * // Returns an array of the levels of each stream. + * log.levels() -> [TRACE, INFO] + * + * // Returns a level of the identified stream. + * log.levels(0) -> TRACE // level of stream at index 0 + * log.levels("foo") // level of stream with name "foo" + * + * Set Usage: + * log.levels(0, INFO) // set level of stream 0 to INFO + * log.levels(0, "info") // can use "info" et al aliases + * log.levels("foo", WARN) // set stream named "foo" to WARN + * + * Stream names: When streams are defined, they can optionally be given + * a name. For example, + * log = new Logger({ + * streams: [ + * { + * name: 'foo', // <--- proposed new option, yucky controlling uniqueness + * path: '/var/log/my-service/foo.log' + * level: 'trace' + * }, + * ... + * + * @param name {String|Number} The stream index or name. + * @param value {Number|String} The level value (INFO) or alias ("info"). + * If not given, this is a 'get' operation. + * @throws {Error} If there is no stream with the given name. + */ +Logger.prototype.levels = function levels(name, value) { + if (name === undefined) { + assert.equal(value, undefined); + return this.streams.map(function (s) { return s.level }); + } + var stream; + if (typeof name === 'number') { + stream = this.streams[name]; + if (stream === undefined) { + throw new Error('invalid stream index: ' + name); + } + } else { + var len = this.streams.length; + for (var i = 0; i < len; i++) { + var s = this.streams[i]; + if (s.name === name) { + stream = s; + break; + } + } + if (!stream) { + throw new Error(format('no stream with name "%s"', name)); + } + } + if (value === undefined) { + return stream.level; + } else { + var newLevel = resolveLevel(value); + stream.level = newLevel; + if (newLevel < this._level) { + this._level = newLevel; + } + } +} + + /** * Apply registered serializers to the appropriate keys in the given fields. * @@ -418,9 +511,9 @@ Logger.prototype._applySerializers = function (fields, keys) { applyKeys[keys[i]] = true; } } - + xxx('_applySerializers: applyKeys', applyKeys); - + // Check each serializer against these (presuming number of serializers // is typically less than number of fields). Object.keys(this.serializers).forEach(function (name) { @@ -760,7 +853,13 @@ var errSerializer = Logger.stdSerializers.err = function err(err) { //---- Exports module.exports = Logger; + +module.exports.TRACE = TRACE; +module.exports.DEBUG = DEBUG; +module.exports.INFO = INFO; +module.exports.WARN = WARN; +module.exports.ERROR = ERROR; +module.exports.FATAL = FATAL; + module.exports.VERSION = VERSION; module.exports.LOG_VERSION = LOG_VERSION; - -