From ae3403f0fb1298b45d0b495e0963c1da66d1b8b3 Mon Sep 17 00:00:00 2001 From: James Simone Date: Thu, 12 Oct 2023 16:55:06 -0400 Subject: [PATCH] Fixes #518 by calling saveLog() as soon as createLogger() has finished loading for pending log messages. --- .../lwc/logger/logEntryBuilder.js | 2 +- .../logger-engine/lwc/logger/loggerService.js | 79 +++++++++++++------ .../loggerDemo/__tests__/loggerDemo.test.js | 45 +++++++++++ .../lwc/loggerDemo/loggerDemo.html | 15 ++++ .../extra-tests/lwc/loggerDemo/loggerDemo.js | 27 +++++++ .../lwc/loggerDemo/loggerDemo.js-meta.xml | 8 ++ 6 files changed, 153 insertions(+), 23 deletions(-) create mode 100644 nebula-logger/extra-tests/lwc/loggerDemo/__tests__/loggerDemo.test.js create mode 100644 nebula-logger/extra-tests/lwc/loggerDemo/loggerDemo.html create mode 100644 nebula-logger/extra-tests/lwc/loggerDemo/loggerDemo.js create mode 100644 nebula-logger/extra-tests/lwc/loggerDemo/loggerDemo.js-meta.xml diff --git a/nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js b/nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js index e5774b03c..7e552072e 100644 --- a/nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js +++ b/nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js @@ -138,7 +138,7 @@ const LogEntryBuilder = class { /* eslint-disable no-console */ _logToConsole() { this.#settingsPromise().then(setting => { - this.isConsoleLoggingEnabled = setting.isConsoleLoggingEnabled; + this.isConsoleLoggingEnabled = !!setting?.isConsoleLoggingEnabled; if (!this.isConsoleLoggingEnabled) { return; diff --git a/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js b/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js index 63004d0da..ad947c2fc 100644 --- a/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js +++ b/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js @@ -7,10 +7,17 @@ import { newLogEntry } from './logEntryBuilder'; import getSettings from '@salesforce/apex/ComponentLogger.getSettings'; import saveComponentLogEntries from '@salesforce/apex/ComponentLogger.saveComponentLogEntries'; +const LOADING_ENUM = { + loading: 'loading', + enabled: 'enabled', + disabled: 'disabled' +}; + /* eslint-disable @lwc/lwc/no-dupe-class-members */ const LoggerService = class { static settings = undefined; + #isSavingLog = false; #componentLogEntries = []; #scenario; #loggingPromises = []; @@ -113,18 +120,21 @@ const LoggerService = class { * @return {Integer} The buffer's current size */ getBufferSize() { - return this.#componentLogEntries.length; + return this.#componentLogEntries.length + this.#loggingPromises.length; } /** * @description Discards any entries that have been generated but not yet saved * @return {Promise} A promise to clear the entries */ - flushBuffer() { - return Promise.all(this.#loggingPromises).then(() => { - this.#componentLogEntries = []; - this.#loggingPromises = []; - }); + async flushBuffer() { + return Promise.all(this.#loggingPromises) + .then(() => { + this.#componentLogEntries = []; + this.#loggingPromises = []; + this.#isSavingLog = false; + }) + .catch(err => console.error(err)); } /** @@ -132,8 +142,17 @@ const LoggerService = class { * All subsequent calls to saveLog() will use the transaction save method * @param {String} saveMethod The enum value of LoggerService.SaveMethod to use for this specific save action */ - saveLog(saveMethodName) { - if (this.getBufferSize() > 0) { + async saveLog(saveMethodName) { + this.#isSavingLog = true; + + const filteredLogEntries = this.#componentLogEntries.filter( + possibleLogEntry => + (possibleLogEntry.loadingEnum === LOADING_ENUM.loading && + this._meetsUserLoggingLevel(possibleLogEntry.loggingLevel) === LOADING_ENUM.enabled) || + possibleLogEntry.loadingEnum === LOADING_ENUM.enabled + ); + + if (filteredLogEntries.length > 0) { let resolvedSaveMethodName; if (!saveMethodName && LoggerService.settings && LoggerService.settings.defaultSaveMethodName) { resolvedSaveMethodName = LoggerService.settings.defaultSaveMethodName; @@ -141,8 +160,13 @@ const LoggerService = class { resolvedSaveMethodName = saveMethodName; } - Promise.all(this.#loggingPromises) - .then(saveComponentLogEntries({ componentLogEntries: this.#componentLogEntries, saveMethodName: resolvedSaveMethodName })) + return Promise.all(this.#loggingPromises) + .then( + saveComponentLogEntries({ + componentLogEntries: filteredLogEntries, + saveMethodName: resolvedSaveMethodName + }) + ) .then(this.flushBuffer()) .catch(error => { if (LoggerService.settings.isConsoleLoggingEnabled === true) { @@ -173,27 +197,38 @@ const LoggerService = class { } _meetsUserLoggingLevel(logEntryLoggingLevel) { - let logEntryLoggingLevelOrdinal = LoggerService.settings.supportedLoggingLevels[logEntryLoggingLevel]; - return ( - LoggerService.settings && - LoggerService.settings.isEnabled === true && - LoggerService.settings.userLoggingLevel.ordinal <= logEntryLoggingLevelOrdinal - ); + if (LoggerService.settings && LoggerService.settings.supportedLoggingLevels && LoggerService.settings.userLoggingLevel) { + const currentIsEnabled = + LoggerService.settings.isEnabled === true && + LoggerService.settings.userLoggingLevel.ordinal <= LoggerService.settings?.supportedLoggingLevels[logEntryLoggingLevel]; + return currentIsEnabled ? LOADING_ENUM.enabled : LOADING_ENUM.disabled; + } + return LOADING_ENUM.loading; } _newEntry(loggingLevel, message) { - // Builder is returned immediately but console log will be determined after loadding settings from server + // Builder is returned immediately but console log will be determined after loading settings from server const logEntryBuilder = newLogEntry(loggingLevel, this._loadSettingsFromServer); logEntryBuilder.setMessage(message); if (this.#scenario) { logEntryBuilder.scenario = this.#scenario; } - const loggingPromise = this._loadSettingsFromServer().then(() => { - if (this._meetsUserLoggingLevel(loggingLevel) === true) { - this.#componentLogEntries.push(logEntryBuilder.getComponentLogEntry()); - } - }); + const loggingPromise = this._loadSettingsFromServer() + .then(() => { + const isEnabledEnum = this._meetsUserLoggingLevel(loggingLevel); + if (isEnabledEnum === LOADING_ENUM.enabled || isEnabledEnum === LOADING_ENUM.loading) { + const componentLogEntry = logEntryBuilder.getComponentLogEntry(); + componentLogEntry.loadingEnum = isEnabledEnum; + this.#componentLogEntries.push(componentLogEntry); + } + if (this.#isSavingLog) { + this.#isSavingLog = false; + this.saveLog(); + } + }) + .catch(err => console.error('there was an error building the logging promise: ' + err.message)); this.#loggingPromises.push(loggingPromise); + return logEntryBuilder; } }; diff --git a/nebula-logger/extra-tests/lwc/loggerDemo/__tests__/loggerDemo.test.js b/nebula-logger/extra-tests/lwc/loggerDemo/__tests__/loggerDemo.test.js new file mode 100644 index 000000000..9b2b70f55 --- /dev/null +++ b/nebula-logger/extra-tests/lwc/loggerDemo/__tests__/loggerDemo.test.js @@ -0,0 +1,45 @@ +import { createElement } from 'lwc'; +import LoggerDemo from 'c/loggerDemo'; + +import getSettings from '@salesforce/apex/ComponentLogger.getSettings'; + +const flushPromises = async () => { + await new Promise(process.nextTick); +}; + +const MOCK_GET_SETTINGS = { + defaultSaveMethod: 'EVENT_BUS', + isEnabled: true, + isConsoleLoggingEnabled: true, + supportedLoggingLevels: { FINEST: 2, FINER: 3, FINE: 4, DEBUG: 5, INFO: 6, WARN: 7, ERROR: 8 }, + userLoggingLevel: { ordinal: 2, name: 'FINEST' } +}; + +jest.mock( + '@salesforce/apex/ComponentLogger.getSettings', + () => { + return { + default: jest.fn() + }; + }, + { virtual: true } +); + +describe('logger demo tests', () => { + afterEach(() => { + while (document.body.firstChild) { + document.body.removeChild(document.body.firstChild); + } + jest.clearAllMocks(); + }); + + it('mounts and saves log correctly in one go', async () => { + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const demo = createElement('c-logger-demo', { is: LoggerDemo }); + document.body.appendChild(demo); + + await flushPromises(); + + expect(demo.logger?.getBufferSize()).toBe(0); + }); +}); diff --git a/nebula-logger/extra-tests/lwc/loggerDemo/loggerDemo.html b/nebula-logger/extra-tests/lwc/loggerDemo/loggerDemo.html new file mode 100644 index 000000000..ad1f4985f --- /dev/null +++ b/nebula-logger/extra-tests/lwc/loggerDemo/loggerDemo.html @@ -0,0 +1,15 @@ + + + diff --git a/nebula-logger/extra-tests/lwc/loggerDemo/loggerDemo.js b/nebula-logger/extra-tests/lwc/loggerDemo/loggerDemo.js new file mode 100644 index 000000000..3f0886bba --- /dev/null +++ b/nebula-logger/extra-tests/lwc/loggerDemo/loggerDemo.js @@ -0,0 +1,27 @@ +//------------------------------------------------------------------------------------------------// +// This file is part of the Nebula Logger project, released under the MIT License. // +// See LICENSE file or go to https://github.com/jongpie/NebulaLogger for full license details. // +//------------------------------------------------------------------------------------------------// + +/* eslint-disable no-console */ +import { LightningElement, api } from 'lwc'; + +import { createLogger } from 'c/logger'; + +export default class LoggerDemo extends LightningElement { + @api + logger; + + connectedCallback() { + this.logger = createLogger(); + console.log('>>> start of connectedCallback()'); + try { + throw new Error('A bad thing happened here'); + } catch (error) { + this.logger.error('>>> connectedCallback error: ' + JSON.stringify(error)); + this.logger.saveLog().then(() => { + console.log('done with async save'); + }); + } + } +} diff --git a/nebula-logger/extra-tests/lwc/loggerDemo/loggerDemo.js-meta.xml b/nebula-logger/extra-tests/lwc/loggerDemo/loggerDemo.js-meta.xml new file mode 100644 index 000000000..bb98fb7d8 --- /dev/null +++ b/nebula-logger/extra-tests/lwc/loggerDemo/loggerDemo.js-meta.xml @@ -0,0 +1,8 @@ + + + 58.0 + true + + lightning__Tab + +