diff --git a/lib/winston/logger.js b/lib/winston/logger.js index 30d7dc0f4..b1b5ce969 100644 --- a/lib/winston/logger.js +++ b/lib/winston/logger.js @@ -44,30 +44,10 @@ class Logger extends Transform { child(defaultRequestMetadata) { const logger = this; - return Object.create(logger, { - write: { - value: function (info) { - const infoClone = Object.assign( - {}, - defaultRequestMetadata, - info - ); - - // Object.assign doesn't copy inherited Error - // properties so we have to do that explicitly - // - // Remark (indexzero): we should remove this - // since the errors format will handle this case. - // - if (info instanceof Error) { - infoClone.stack = info.stack; - infoClone.message = info.message; - } - - logger.write(infoClone); - } - } - }); + const childLogger = Object.create(logger); + childLogger.parentLogger = logger; + childLogger.defaultMeta = defaultRequestMetadata; + return childLogger; } /** @@ -117,6 +97,7 @@ class Logger extends Transform { this.rejections = new RejectionHandler(this); this.profilers = {}; this.exitOnError = exitOnError; + this.parentLogger = null; // Add all transports we have been provided. if (transports) { @@ -233,34 +214,28 @@ class Logger extends Transform { } const [meta] = splat; + const info = {}; + this._addDefaultMeta(info); + let mergeMessage = false; if (typeof meta === 'object' && meta !== null) { // Extract tokens, if none available default to empty array to // ensure consistancy in expected results const tokens = msg && msg.match && msg.match(formatRegExp); if (!tokens) { - const info = Object.assign({}, this.defaultMeta, meta, { - [LEVEL]: level, - [SPLAT]: splat, - level, - message: msg - }); - - if (meta.message) info.message = `${info.message} ${meta.message}`; - if (meta.stack) info.stack = meta.stack; - - this.write(info); - return this; + assignWithGetters(info, meta); + mergeMessage = true; } } - - this.write(Object.assign({}, this.defaultMeta, { + assignWithGetters(info, { [LEVEL]: level, [SPLAT]: splat, level, message: msg - })); - + }); + if (mergeMessage && meta.message) info.message = `${info.message} ${meta.message}`; + if (mergeMessage && meta.stack) info.stack = meta.stack; + this.write(info); return this; } @@ -624,6 +599,13 @@ class Logger extends Transform { ); } + write(chunk, ...args) { + if (!(chunk instanceof Error)) { + chunk = Object.assign({}, chunk); + } + super.write(chunk, ...args); + } + /** * Bubbles the `event` that occured on the specified `transport` up * from this instance. @@ -647,9 +629,12 @@ class Logger extends Transform { } _addDefaultMeta(msg) { - if (this.defaultMeta) { - Object.assign(msg, this.defaultMeta); + const merged = {}; + if (this.parentLogger) { + this.parentLogger._addDefaultMeta(merged); } + assignWithGetters(merged, this.defaultMeta, msg); + assignWithGetters(msg, merged); } } @@ -674,4 +659,20 @@ Object.defineProperty(Logger.prototype, 'transports', { } }); +function assignWithGetters(target, ...sources) { + // eslint-disable-next-line no-shadow + return sources.reduce((target, source) => { + // eslint-disable-next-line eqeqeq + if (source != null) { + const enumerableKeys = Object.keys(source); + const symbolKeys = Object.getOwnPropertySymbols(source); + // Don't use Reflect.ownKeys here because we only want to assign enumerable ones + for (const key of enumerableKeys.concat(symbolKeys)) { + Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); + } + } + return target; + }, target); +} + module.exports = Logger; diff --git a/test/logger.test.js b/test/logger.test.js index 2618d1d60..92dc157a3 100755 --- a/test/logger.test.js +++ b/test/logger.test.js @@ -68,38 +68,33 @@ describe('Logger', function () { }); it('new Logger({ levels }) custom methods are not bound to instance', function (done) { - var logger = winston.createLogger({ - level: 'error', - exitOnError: false, - transports: [] - }); - let logs = []; - let extendedLogger = Object.create(logger, { - write: { - value: function(...args) { - logs.push(args); - if (logs.length === 4) { - assume(logs.length).is.eql(4); - assume(logs[0]).is.eql([{ test: 1, level: 'info' }]); - assume(logs[1]).is.eql([{ test: 2, level: 'warn' }]); - assume(logs[2]).is.eql([{ message: 'test3', level: 'info' }]) - assume(logs[3]).is.eql([{ with: 'meta', - test: 4, - level: 'warn', - message: 'a warning' - }]); - - done(); - } - } + var expected = [ + { test: 1, level: 'info' }, + { test: 2, level: 'warn' }, + { message: 'test3', level: 'info' }, + { with: 'meta', + test: 4, + level: 'warn', + message: 'a warning' + } + ]; + + var i = 0; + var logger = helpers.createLogger(function (chunk, encoding, next) { + assume(i).is.lessThan(expected.length); + assume(chunk).is.eql(expected[i]); + i++; + next(); + if (i === expected.length) { + done(); } }); - extendedLogger.log('info', { test: 1 }); - extendedLogger.log('warn', { test: 2 }); - extendedLogger.info('test3'); - extendedLogger.warn('a warning', { with: 'meta', test: 4 }); + logger.log('info', { test: 1 }); + logger.log('warn', { test: 2 }); + logger.info('test3'); + logger.warn('a warning', { with: 'meta', test: 4 }); }); it('.add({ invalid Transport })', function () {