From 22c7d224de6cb46a5aef1a5071eb53a37c749185 Mon Sep 17 00:00:00 2001 From: Zachary Haber Date: Fri, 17 Jun 2022 14:24:28 -0500 Subject: [PATCH 01/10] chore: separate out CallStack type from base LoggingEvent --- types/log4js.d.ts | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/types/log4js.d.ts b/types/log4js.d.ts index bebb1fa..a49ce5e 100644 --- a/types/log4js.d.ts +++ b/types/log4js.d.ts @@ -74,8 +74,20 @@ export interface Level { level: number; levelStr: string; } - -export interface LoggingEvent { +/** + * A parsed CallStack from an `Error.stack` trace + */ +export interface CallStack { + functionName: string; + fileName: string; + lineNumber: number; + columnNumber: number; + /** + * The stack string after the skipped lines + */ + callStack: string; +} +export interface LoggingEvent extends Partial { categoryName: string; // name of category level: Level; // level of message data: any[]; // objects to log @@ -86,11 +98,6 @@ export interface LoggingEvent { workerId: number; worker: number; }; - functionName?: string; - fileName?: string; - lineNumber?: number; - columnNumber?: number; - callStack?: string; serialise(): string; } @@ -432,7 +439,21 @@ export interface Logger { clearContext(): void; - setParseCallStackFunction(parseFunction: Function): void; + /** + * Replace the basic parse function with a new custom one + * - Note that linesToSkip will be based on the origin of the Error object in addition to the callStackLinesToSkip (at least 1) + * @param parseFunction the new parseFunction. Use `undefined` to reset to the base implementation + */ + setParseCallStackFunction( + parseFunction: (error: Error, linesToSkip: number) => CallStack | undefined + ): void; + + /** + * Adjust the value of linesToSkip when the parseFunction is called. + * + * Cannot be less than 0. + */ + callStackLinesToSkip: number; trace(message: any, ...args: any[]): void; From 89b8a58f3f9e39ab91fecc9c8ce15d41d0f2fd0b Mon Sep 17 00:00:00 2001 From: Zachary Haber Date: Fri, 17 Jun 2022 14:31:54 -0500 Subject: [PATCH 02/10] feat: use data Error for callStack information use the first data value if an Error to get callstack info. Only 1 line will be stripped from it by default Refs: #1268 --- lib/logger.js | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/lib/logger.js b/lib/logger.js index 3db4243..f1da46b 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -8,10 +8,32 @@ const categories = require('./categories'); const configuration = require('./configuration'); const stackReg = /at (?:(.+)\s+\()?(?:(.+?):(\d+)(?::(\d+))?|([^)]+))\)?/; +/** + * The top entry is the Error + */ +const baseCallStackSkip = 1; +/** + * The _log function is 3 levels deep, we need to skip those to make it to the callSite + */ +const defaultErrorCallStackSkip = 3; -function defaultParseCallStack(data, skipIdx = 4) { +/** + * + * @param {Error} data + * @param {number} skipIdx + * @returns {import('../types/log4js').CallStack | null} + */ +function defaultParseCallStack( + data, + skipIdx = defaultErrorCallStackSkip + baseCallStackSkip +) { try { const stacklines = data.stack.split('\n').slice(skipIdx); + if (!stacklines.length) { + // There's no stack in this stack + // Should we try a previous index if skipIdx was set? + return null; + } const lineMatch = stackReg.exec(stacklines[0]); /* istanbul ignore else: failsafe */ if (lineMatch && lineMatch.length === 6) { @@ -71,6 +93,7 @@ class Logger { } this.category = name; this.context = {}; + /** @private */ this.parseCallStack = defaultParseCallStack; debug(`Logger created (${this.category}, ${this.level})`); } @@ -123,12 +146,28 @@ class Logger { _log(level, data) { debug(`sending log data (${level}) to appenders`); + let callStack; + if (this.useCallStack) { + try { + if (data[0] instanceof Error) { + callStack = this.parseCallStack(data[0], baseCallStackSkip); + } + } catch (_err) { + // Ignore Error + } + callStack = + callStack || + this.parseCallStack( + new Error(), + defaultErrorCallStackSkip + baseCallStackSkip + ); + } const loggingEvent = new LoggingEvent( this.category, level, data, this.context, - this.useCallStack && this.parseCallStack(new Error()) + callStack ); clustering.send(loggingEvent); } From a17c8e5e6f7ffdd5cf0d4e46a51493891070654b Mon Sep 17 00:00:00 2001 From: Zachary Haber Date: Fri, 17 Jun 2022 14:33:51 -0500 Subject: [PATCH 03/10] feat: allow setting how deeply a logger should search into the stack `logger.callStackLinesToSkip = 2` will make it skip 2 extra lines above the base. You can't skip negative lines Refs: #1268 --- lib/logger.js | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/logger.js b/lib/logger.js index f1da46b..80b056a 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -94,6 +94,8 @@ class Logger { this.category = name; this.context = {}; /** @private */ + this.callStackSkipIndex = 0; + /** @private */ this.parseCallStack = defaultParseCallStack; debug(`Logger created (${this.category}, ${this.level})`); } @@ -120,6 +122,20 @@ class Logger { categories.setEnableCallStackForCategory(this.category, bool === true); } + get callStackLinesToSkip() { + return this.callStackSkipIndex; + } + + set callStackLinesToSkip(number) { + if (typeof number !== 'number') { + throw new TypeError('Must be a number'); + } + if (number < 0) { + throw new RangeError('Must be >= 0'); + } + this.callStackSkipIndex = number; + } + log(level, ...args) { const logLevel = levels.getLevel(level); if (!logLevel) { @@ -150,7 +166,10 @@ class Logger { if (this.useCallStack) { try { if (data[0] instanceof Error) { - callStack = this.parseCallStack(data[0], baseCallStackSkip); + callStack = this.parseCallStack( + data[0], + this.callStackSkipIndex + baseCallStackSkip + ); } } catch (_err) { // Ignore Error @@ -159,7 +178,9 @@ class Logger { callStack || this.parseCallStack( new Error(), - defaultErrorCallStackSkip + baseCallStackSkip + this.callStackSkipIndex + + defaultErrorCallStackSkip + + baseCallStackSkip ); } const loggingEvent = new LoggingEvent( From d53495a8abf750e17ae659e7593af342ab933a95 Mon Sep 17 00:00:00 2001 From: Zachary Haber Date: Fri, 17 Jun 2022 14:36:24 -0500 Subject: [PATCH 04/10] fix: setParseCallStackFunction will no longer allow invalid values if set to an invalid value, it'll throw an error immediately, instead of later. --- lib/logger.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/logger.js b/lib/logger.js index 80b056a..b4b2373 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -206,7 +206,11 @@ class Logger { } setParseCallStackFunction(parseFunction) { - this.parseCallStack = parseFunction; + if (typeof parseFunction === 'function') { + this.parseCallStack = parseFunction; + } else { + throw new TypeError('Invalid type passed to setParseCallStackFunction'); + } } } From b139aafcab36201e4b6ee4c970bf28fcc4c010cb Mon Sep 17 00:00:00 2001 From: Zachary Haber Date: Fri, 17 Jun 2022 14:37:10 -0500 Subject: [PATCH 05/10] feat: setParseCallStackFunction(undefined) will restore the default behavior --- lib/logger.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/logger.js b/lib/logger.js index b4b2373..0e409a7 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -208,6 +208,8 @@ class Logger { setParseCallStackFunction(parseFunction) { if (typeof parseFunction === 'function') { this.parseCallStack = parseFunction; + } else if (parseFunction === undefined) { + this.parseCallStack = defaultParseCallStack; } else { throw new TypeError('Invalid type passed to setParseCallStackFunction'); } From 046589441213e86277c07c4389c70fac5762c31c Mon Sep 17 00:00:00 2001 From: Zachary Haber Date: Fri, 17 Jun 2022 14:37:52 -0500 Subject: [PATCH 06/10] chore: tests for new parseCallStack functionality and changes --- test/tap/logger-test.js | 109 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/test/tap/logger-test.js b/test/tap/logger-test.js index 6340369..b1fba7a 100644 --- a/test/tap/logger-test.js +++ b/test/tap/logger-test.js @@ -5,8 +5,16 @@ const callsites = require('callsites'); const levels = require('../../lib/levels'); const categories = require('../../lib/categories'); +/** @type {import('../../types/log4js').LoggingEvent[]} */ const events = []; +/** @type {string[]} */ const messages = []; + +/** + * @typedef {import('../../types/log4js').Logger} LoggerClass + */ + +/** @type {{new (): LoggerClass}} */ const Logger = sandbox.require('../../lib/logger', { requires: { './levels': levels, @@ -147,6 +155,14 @@ test('../../lib/logger', (batch) => { t.equal(logger.useCallStack, false); + logger.debug('test no callStack'); + let event = events.shift(); + t.notMatch(event, { functionName: String }); + t.notMatch(event, { fileName: String }); + t.notMatch(event, { lineNumber: Number }); + t.notMatch(event, { columnNumber: Number }); + t.notMatch(event, { callStack: String }); + logger.useCallStack = false; t.equal(logger.useCallStack, false); @@ -167,6 +183,15 @@ test('../../lib/logger', (batch) => { logger.useCallStack = true; t.equal(logger.useCallStack, true); + logger.debug('test with callStack'); + event = events.shift(); + t.match(event, { + functionName: String, + fileName: String, + lineNumber: Number, + columnNumber: Number, + callStack: String, + }); t.end(); } ); @@ -250,6 +275,9 @@ test('../../lib/logger', (batch) => { t.notOk(results); t.equal(messages.length, 1, 'should have error'); + results = logger.parseCallStack(new Error(), 100); + t.equal(results, null); + t.end(); }); @@ -309,6 +337,11 @@ test('../../lib/logger', (batch) => { batch.test('should correctly change the parseCallStack function', (t) => { const logger = new Logger('stack'); + logger.level = 'debug'; + logger.useCallStack = true; + + logger.info('test defaultParseCallStack'); + const initialEvent = events.shift(); const parseFunction = function () { return { functionName: 'test function name', @@ -318,8 +351,6 @@ test('../../lib/logger', (batch) => { callStack: 'test callstack', }; }; - logger.level = 'debug'; - logger.useCallStack = true; logger.setParseCallStackFunction(parseFunction); t.equal(logger.parseCallStack, parseFunction); @@ -331,9 +362,83 @@ test('../../lib/logger', (batch) => { t.equal(events[0].columnNumber, 25); t.equal(events[0].callStack, 'test callstack'); + events.shift(); + + logger.setParseCallStackFunction(undefined); + logger.info('test restoredDefaultParseCallStack'); + + t.equal(events[0].functionName, initialEvent.functionName); + t.equal(events[0].fileName, initialEvent.fileName); + t.equal(events[0].columnNumber, initialEvent.columnNumber); + + t.throws(() => logger.setParseCallStackFunction('not a function')); + t.end(); }); + batch.test('should correctly change the stack levels to skip', (t) => { + const logger = new Logger('stack'); + logger.level = 'debug'; + logger.useCallStack = true; + + t.equal( + logger.callStackLinesToSkip, + 0, + 'initial callStackLinesToSkip changed' + ); + + logger.info('get initial stack'); + const initialEvent = events.shift(); + const newStackSkip = 1; + logger.callStackLinesToSkip = newStackSkip; + t.equal(logger.callStackLinesToSkip, newStackSkip); + logger.info('test stack skip'); + const event = events.shift(); + t.not(event.functionName, initialEvent.functionName); + t.not(event.fileName, initialEvent.fileName); + t.equal( + event.callStack, + initialEvent.callStack.split('\n').slice(newStackSkip).join('\n') + ); + + t.throws(() => { + logger.callStackLinesToSkip = -1; + }); + t.throws(() => { + logger.callStackLinesToSkip = '2'; + }); + t.end(); + }); + + batch.test( + "should run parseCallStack against the first data value if it' an error", + (t) => { + const logger = new Logger('stack'); + logger.level = 'debug'; + logger.useCallStack = true; + + const error = new Error(); + + logger.info(error); + const event = events.shift(); + + logger.info(error); + + t.match(event, events.shift()); + + logger.callStackLinesToSkip = 1; + logger.info(error); + const event2 = events.shift(); + + t.equal( + event2.callStack, + event.callStack.split('\n').slice(1).join('\n') + ); + + t.end(); + } + ); + batch.test('creating/cloning of category', (t) => { const defaultLogger = new Logger('default'); defaultLogger.level = 'trace'; From 4ffbcae8b23f7ce5effeb4045c44b265ef2b416c Mon Sep 17 00:00:00 2001 From: Zachary Haber Date: Fri, 17 Jun 2022 14:49:40 -0500 Subject: [PATCH 07/10] chore: update docs --- docs/api.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/api.md b/docs/api.md index 45b7656..6c3b7da 100644 --- a/docs/api.md +++ b/docs/api.md @@ -21,7 +21,7 @@ Properties: - `categories` (object) - a map of named categories (string) to category definitions (object). You must define the `default` category which is used for all log events that do not match a specific category. Category definitions have two properties: - `appenders` (array of strings) - the list of appender names to be used for this category. A category must have at least one appender. - `level` (string, case insensitive) - the minimum log level that this category will send to the appenders. For example, if set to 'error' then the appenders will only receive log events of level 'error', 'fatal', 'mark' - log events of 'info', 'warn', 'debug', or 'trace' will be ignored. - - `enableCallStack` (boolean, optional, defaults to `false`) - setting this to `true` will make log events for this category use the call stack to generate line numbers and file names in the event. See [pattern layout](layouts.md) for how to output these values in your appenders. + - `enableCallStack` (boolean, optional, defaults to `false`) - setting this to `true` will make log events for this category use the call stack to generate line numbers and file names in the event. See [pattern layout](layouts.md) for how to output these values in your appenders. If you log an Error object as the first parameter, that Error will be used to generate the line numbers and file name instead. - `pm2` (boolean) (optional) - set this to true if you're running your app using [pm2](http://pm2.keymetrics.io), otherwise logs will not work (you'll also need to install pm2-intercom as pm2 module: `pm2 install pm2-intercom`) - `pm2InstanceVar` (string) (optional, defaults to 'NODE_APP_INSTANCE') - set this if you're using pm2 and have changed the default name of the NODE_APP_INSTANCE variable. - `disableClustering` (boolean) (optional) - set this to true if you liked the way log4js used to just ignore clustered environments, or you're having trouble with PM2 logging. Each worker process will do its own logging. Be careful with this if you're logging to files, weirdness can occur. @@ -35,12 +35,13 @@ This function takes a single optional string argument to denote the category to - `addContext(,)` - where `` is a string, `` can be anything. This stores a key-value pair that is added to all log events generated by the logger. Uses would be to add ids for tracking a user through your application. Currently only the `logFaces` appenders make use of the context values. - `removeContext()` - removes a previously defined key-value pair from the context. - `clearContext()` - removes all context pairs from the logger. -- `setParseCallStackFunction(function)` - Allow to override the default way to parse the callstack data for the layout pattern, a generic javascript Error object is passed to the function. Must return an object with properties : `functionName` / `fileName` / `lineNumber` / `columnNumber` / `callStack`. Can for example be used if all of your log call are made from one "debug" class and you would to "erase" this class from the callstack to only show the function which called your "debug" class. +- `setParseCallStackFunction(function | undefined)` - Allow to override the default way to parse the callstack data for the layout pattern, a generic javascript Error object is passed to the function. Must return an object with properties : `functionName` / `fileName` / `lineNumber` / `columnNumber` / `callStack`. Can for example be used if all of your log call are made from one "debug" class and you would to "erase" this class from the callstack to only show the function which called your "debug" class. If you pass `undefined` as the argument, it will be reset to the default parser. The `Logger` object has the following properties: - `level` - where `level` is a log4js level or a string that matches a level (e.g. 'info', 'INFO', etc). This allows overriding the configured level for this logger. Changing this value applies to all loggers of the same category. - `useCallStack` - where `useCallStack` is a boolean to indicate if log events for this category use the call stack to generate line numbers and file names in the event. This allows overriding the configured useCallStack for this logger. Changing this value applies to all loggers of the same category. +- `callStackLinesToSkip` - where `callStackLinesToSkip` is a number (0 by default) that allows you to customize how many lines of the call stack should be skipped when parsing the Error stack. For example, if you call the logger from within a dedicated logging function, you can use `callStackLinesToSkip = 1` to ignore that function when looking at stack traces. ## Shutdown - `log4js.shutdown([callback])` From 0683535e0f61f4c775e056b3469ade5f0e739b8e Mon Sep 17 00:00:00 2001 From: Zachary Haber Date: Fri, 24 Jun 2022 05:52:17 +0800 Subject: [PATCH 08/10] feat: add `error` property to LoggingEvent --- lib/LoggingEvent.js | 42 ++++++++++++++++++++++++----------- test/tap/LoggingEvent-test.js | 28 +++++++++++++++++++++++ types/log4js.d.ts | 4 ++++ 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/lib/LoggingEvent.js b/lib/LoggingEvent.js index 1902751..780c9eb 100644 --- a/lib/LoggingEvent.js +++ b/lib/LoggingEvent.js @@ -12,15 +12,17 @@ class LoggingEvent { * @param {string} categoryName name of category * @param {Log4js.Level} level level of message * @param {Array} data objects to log + * @param {Error} [error] * @author Seth Chisamore */ - constructor(categoryName, level, data, context, location) { + constructor(categoryName, level, data, context, location, error) { this.startTime = new Date(); this.categoryName = categoryName; this.data = data; this.level = level; this.context = Object.assign({}, context); // eslint-disable-line prefer-object-spread this.pid = process.pid; + this.error = error; if (location) { this.fileName = location.fileName; @@ -76,26 +78,40 @@ class LoggingEvent { } return value; }); - rehydratedEvent.location = { - fileName: rehydratedEvent.fileName, - lineNumber: rehydratedEvent.lineNumber, - columnNumber: rehydratedEvent.columnNumber, - callStack: rehydratedEvent.callStack, - className: rehydratedEvent.className, - functionName: rehydratedEvent.functionName, - functionAlias: rehydratedEvent.functionAlias, - callerName: rehydratedEvent.callerName, - }; + if ( + rehydratedEvent.fileName || + rehydratedEvent.lineNumber || + rehydratedEvent.columnNumber || + rehydratedEvent.callStack || + rehydratedEvent.className || + rehydratedEvent.functionName || + rehydratedEvent.functionAlias || + rehydratedEvent.callerName + ) { + rehydratedEvent.location = { + fileName: rehydratedEvent.fileName, + lineNumber: rehydratedEvent.lineNumber, + columnNumber: rehydratedEvent.columnNumber, + callStack: rehydratedEvent.callStack, + className: rehydratedEvent.className, + functionName: rehydratedEvent.functionName, + functionAlias: rehydratedEvent.functionAlias, + callerName: rehydratedEvent.callerName, + }; + } event = new LoggingEvent( rehydratedEvent.categoryName, levels.getLevel(rehydratedEvent.level.levelStr), rehydratedEvent.data, rehydratedEvent.context, - rehydratedEvent.location + rehydratedEvent.location, + rehydratedEvent.error ); event.startTime = new Date(rehydratedEvent.startTime); event.pid = rehydratedEvent.pid; - event.cluster = rehydratedEvent.cluster; + if (rehydratedEvent.cluster) { + event.cluster = rehydratedEvent.cluster; + } } catch (e) { event = new LoggingEvent('log4js', levels.ERROR, [ 'Unable to parse log:', diff --git a/test/tap/LoggingEvent-test.js b/test/tap/LoggingEvent-test.js index 2ec6c6c..9dc3242 100644 --- a/test/tap/LoggingEvent-test.js +++ b/test/tap/LoggingEvent-test.js @@ -152,5 +152,33 @@ test('LoggingEvent', (batch) => { t.end(); }); + batch.test('Should correctly serialize and deserialize', (t) => { + const error = new Error('test'); + const location = { + fileName: __filename, + lineNumber: 123, + columnNumber: 52, + callStack: error.stack, + className: 'Foo', + functionName: 'test', + functionAlias: 'baz', + callerName: 'Foo.test [as baz]', + }; + const event = new LoggingEvent( + 'cheese', + levels.DEBUG, + [error, 'log message'], + { + user: 'bob', + }, + location, + error + ); + const event2 = LoggingEvent.deserialise(event.serialise()); + t.match(event2, event); + + t.end(); + }); + batch.end(); }); diff --git a/types/log4js.d.ts b/types/log4js.d.ts index a49ce5e..4b16e94 100644 --- a/types/log4js.d.ts +++ b/types/log4js.d.ts @@ -98,6 +98,10 @@ export interface LoggingEvent extends Partial { workerId: number; worker: number; }; + /** + * The first Error object in the data if there is one + */ + error?: Error; serialise(): string; } From b1598c04c73e4aa70d3129e8112d384b34dd5f36 Mon Sep 17 00:00:00 2001 From: Zachary Haber Date: Thu, 23 Jun 2022 17:19:35 -0500 Subject: [PATCH 09/10] feat: add error to logging event from logger --- docs/api.md | 2 +- lib/logger.js | 10 +++++---- test/tap/logger-test.js | 47 +++++++++++++++++++++++------------------ 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/docs/api.md b/docs/api.md index 6c3b7da..cedb658 100644 --- a/docs/api.md +++ b/docs/api.md @@ -21,7 +21,7 @@ Properties: - `categories` (object) - a map of named categories (string) to category definitions (object). You must define the `default` category which is used for all log events that do not match a specific category. Category definitions have two properties: - `appenders` (array of strings) - the list of appender names to be used for this category. A category must have at least one appender. - `level` (string, case insensitive) - the minimum log level that this category will send to the appenders. For example, if set to 'error' then the appenders will only receive log events of level 'error', 'fatal', 'mark' - log events of 'info', 'warn', 'debug', or 'trace' will be ignored. - - `enableCallStack` (boolean, optional, defaults to `false`) - setting this to `true` will make log events for this category use the call stack to generate line numbers and file names in the event. See [pattern layout](layouts.md) for how to output these values in your appenders. If you log an Error object as the first parameter, that Error will be used to generate the line numbers and file name instead. + - `enableCallStack` (boolean, optional, defaults to `false`) - setting this to `true` will make log events for this category use the call stack to generate line numbers and file names in the event. See [pattern layout](layouts.md) for how to output these values in your appenders. If you log an Error object, that Error object (or the first of many) will be used to generate the line numbers and file name instead. - `pm2` (boolean) (optional) - set this to true if you're running your app using [pm2](http://pm2.keymetrics.io), otherwise logs will not work (you'll also need to install pm2-intercom as pm2 module: `pm2 install pm2-intercom`) - `pm2InstanceVar` (string) (optional, defaults to 'NODE_APP_INSTANCE') - set this if you're using pm2 and have changed the default name of the NODE_APP_INSTANCE variable. - `disableClustering` (boolean) (optional) - set this to true if you liked the way log4js used to just ignore clustered environments, or you're having trouble with PM2 logging. Each worker process will do its own logging. Be careful with this if you're logging to files, weirdness can occur. diff --git a/lib/logger.js b/lib/logger.js index 0e409a7..69618c1 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -162,17 +162,18 @@ class Logger { _log(level, data) { debug(`sending log data (${level}) to appenders`); + const error = data.find((item) => item instanceof Error); let callStack; if (this.useCallStack) { try { - if (data[0] instanceof Error) { + if (error) { callStack = this.parseCallStack( - data[0], + error, this.callStackSkipIndex + baseCallStackSkip ); } } catch (_err) { - // Ignore Error + // Ignore Error and use the original method of creating a new Error. } callStack = callStack || @@ -188,7 +189,8 @@ class Logger { level, data, this.context, - callStack + callStack, + error ); clustering.send(loggingEvent); } diff --git a/test/tap/logger-test.js b/test/tap/logger-test.js index b1fba7a..cde4ae6 100644 --- a/test/tap/logger-test.js +++ b/test/tap/logger-test.js @@ -410,34 +410,39 @@ test('../../lib/logger', (batch) => { t.end(); }); - batch.test( - "should run parseCallStack against the first data value if it' an error", - (t) => { - const logger = new Logger('stack'); - logger.level = 'debug'; - logger.useCallStack = true; + batch.test('should utilize the first Error data value', (t) => { + const logger = new Logger('stack'); + logger.level = 'debug'; + logger.useCallStack = true; - const error = new Error(); + const error = new Error(); - logger.info(error); - const event = events.shift(); + logger.info(error); + const event = events.shift(); + t.equal(event.error, error); - logger.info(error); + logger.info(error); - t.match(event, events.shift()); + t.match(event, events.shift()); - logger.callStackLinesToSkip = 1; - logger.info(error); - const event2 = events.shift(); + logger.callStackLinesToSkip = 1; + logger.info(error); + const event2 = events.shift(); - t.equal( - event2.callStack, - event.callStack.split('\n').slice(1).join('\n') - ); + t.equal(event2.callStack, event.callStack.split('\n').slice(1).join('\n')); + logger.callStackLinesToSkip = 0; + logger.info('hi', error); + const event3 = events.shift(); + t.equal(event3.callStack, event.callStack); + t.equal(event3.error, error); - t.end(); - } - ); + logger.info('hi', error, new Error()); + const event4 = events.shift(); + t.equal(event4.callStack, event.callStack); + t.equal(event4.error, error); + + t.end(); + }); batch.test('creating/cloning of category', (t) => { const defaultLogger = new Logger('default'); From 0089e784b4436d29b97cd667f840d3d58d542ac3 Mon Sep 17 00:00:00 2001 From: Zachary Haber Date: Thu, 23 Jun 2022 19:07:31 -0500 Subject: [PATCH 10/10] refactor: remove duck typing --- lib/LoggingEvent.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/LoggingEvent.js b/lib/LoggingEvent.js index 780c9eb..42bc5e0 100644 --- a/lib/LoggingEvent.js +++ b/lib/LoggingEvent.js @@ -39,9 +39,8 @@ class LoggingEvent { serialise() { return flatted.stringify(this, (key, value) => { // JSON.stringify(new Error('test')) returns {}, which is not really useful for us. - // The following allows us to serialize errors correctly. - // duck-typing for Error object - if (value && value.message && value.stack) { + // The following allows us to serialize errors (semi) correctly. + if (value instanceof Error) { // eslint-disable-next-line prefer-object-spread value = Object.assign( { message: value.message, stack: value.stack },