From 2f30510fce8c3ef516f0ff6766c8eb4c4856636c Mon Sep 17 00:00:00 2001 From: Jonathan Gillespie Date: Fri, 20 Sep 2024 00:31:30 -0400 Subject: [PATCH] [WIP] --- .../__tests__/loggerServiceTaskQueue.test.js | 59 +++++++++ .../lwc/logger/logEntryBuilder.js | 26 +--- .../main/logger-engine/lwc/logger/logger.js | 9 +- .../logger-engine/lwc/logger/loggerService.js | 123 +++++++++++------- .../lwc/logger/loggerServiceTaskQueue.js | 30 +++++ .../loggerLWCImportDemo.html | 4 +- .../loggerLWCImportDemo.js | 8 +- 7 files changed, 176 insertions(+), 83 deletions(-) create mode 100644 nebula-logger/core/main/logger-engine/lwc/logger/__tests__/loggerServiceTaskQueue.test.js create mode 100644 nebula-logger/core/main/logger-engine/lwc/logger/loggerServiceTaskQueue.js diff --git a/nebula-logger/core/main/logger-engine/lwc/logger/__tests__/loggerServiceTaskQueue.test.js b/nebula-logger/core/main/logger-engine/lwc/logger/__tests__/loggerServiceTaskQueue.test.js new file mode 100644 index 000000000..c01cc7241 --- /dev/null +++ b/nebula-logger/core/main/logger-engine/lwc/logger/__tests__/loggerServiceTaskQueue.test.js @@ -0,0 +1,59 @@ +import { LoggerServiceTaskQueue } from '../loggerServiceTaskQueue'; + +describe('logger service task queue tests', () => { + afterEach(async () => { + jest.clearAllMocks(); + }); + + it('correctly resolves a single async task', async () => { + const input = 'some string'; + const asyncFunction = async input => { + const output = 'Your input was: ' + input; + return output; + }; + const loggerServiceTaskQueue = new LoggerServiceTaskQueue(); + + loggerServiceTaskQueue.enqueueTask(asyncFunction, input); + const processedTasks = await loggerServiceTaskQueue.processTaskQueue(); + + expect(processedTasks.length).toEqual(1); + expect(processedTasks[0].fn).toEqual(asyncFunction); + expect(processedTasks[0].args.length).toEqual(1); + expect(processedTasks[0].args[0]).toEqual(input); + expect(processedTasks[0].output).toEqual('Your input was: ' + input); + }); + + it('correctly processes sync and async tasks in order', async () => { + const pendingTasks = []; + for (let i = 0; i < 3; i++) { + const task = { + isAsync: false, + fn: () => { + return i; + }, + args: [i] + }; + // Make one task, somewhere in the middle of the queue, an async task + if (i == 1) { + task.isAsync = true; + task.fn = async () => { + return i; + }; + } + pendingTasks.push(task); + } + const loggerServiceTaskQueue = new LoggerServiceTaskQueue(); + + pendingTasks.forEach(task => { + loggerServiceTaskQueue.enqueueTask(task.fn, task.args); + }); + const processedTasks = await loggerServiceTaskQueue.processTaskQueue(); + + expect(processedTasks.length).toEqual(3); + const expectedCombinedOutput = '012'; + const actualCombinedOutput = processedTasks.reduce((accumulator, currentTask) => { + return accumulator + currentTask.output; + }, ''); + expect(actualCombinedOutput).toEqual(expectedCombinedOutput); + }); +}); 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 1150bda94..86c227156 100644 --- a/nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js +++ b/nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js @@ -49,20 +49,14 @@ const ComponentLogEntry = class { /* eslint-disable @lwc/lwc/no-dupe-class-members */ const LogEntryBuilder = class { #componentLogEntry; - #isConsoleLoggingEnabled; - #isLightningLoggerEnabled; /** * @description Constructor used to generate each JavaScript-based log entry event * This class is the JavaScript-equivalent of the Apex class `LogEntryBuilder` * @param {String} loggingLevel The `LoggingLevel` enum to use for the builder's instance of `LogEntryEvent__e` - * @param {Boolean} isConsoleLoggingEnabled Determines if `console.log()` methods are execute - * @param {Boolean} isLightningLoggerEnabled Determines if `lightning-logger` LWC is called */ - constructor(loggingLevel, isConsoleLoggingEnabled, isLightningLoggerEnabled) { + constructor(loggingLevel) { this.#componentLogEntry = new ComponentLogEntry(loggingLevel); - this.#isConsoleLoggingEnabled = isConsoleLoggingEnabled; - this.#isLightningLoggerEnabled = isLightningLoggerEnabled; } /** @@ -72,8 +66,6 @@ const LogEntryBuilder = class { */ setMessage(message) { this.#componentLogEntry.message = message; - this._logToConsole(); - this._logToLightningLogger(); return this; } @@ -195,11 +187,7 @@ const LogEntryBuilder = class { } /* eslint-disable no-console */ - _logToConsole() { - if (!this.#isConsoleLoggingEnabled) { - return; - } - + logToConsole() { const consoleMessagePrefix = `%c Nebula Logger ${CURRENT_VERSION_NUMBER} `; const consoleFormatting = 'background: #0c598d; color: #fff; font-size: 12px; font-weight:bold;'; let consoleLoggingFunction; @@ -244,15 +232,13 @@ const LogEntryBuilder = class { ); } - _logToLightningLogger() { - if (this.#isLightningLoggerEnabled) { - lightningLog(this.#componentLogEntry); - } + logToLightningLogger() { + lightningLog(this.#componentLogEntry); } }; let hasInitialized = false; -export function newLogEntry(loggingLevel, isConsoleLoggingEnabled, isLightningLoggerEnabled) { +export function newLogEntry(loggingLevel) { if (!hasInitialized) { const consoleMessagePrefix = `%c Nebula Logger ${CURRENT_VERSION_NUMBER} `; const consoleFormatting = 'background: #0c598d; color: #fff; font-size: 12px; font-weight:bold;'; @@ -262,5 +248,5 @@ export function newLogEntry(loggingLevel, isConsoleLoggingEnabled, isLightningLo hasInitialized = true; } - return new LogEntryBuilder(loggingLevel, isConsoleLoggingEnabled, isLightningLoggerEnabled); + return new LogEntryBuilder(loggingLevel); } diff --git a/nebula-logger/core/main/logger-engine/lwc/logger/logger.js b/nebula-logger/core/main/logger-engine/lwc/logger/logger.js index 7015f83c0..246a19bd9 100644 --- a/nebula-logger/core/main/logger-engine/lwc/logger/logger.js +++ b/nebula-logger/core/main/logger-engine/lwc/logger/logger.js @@ -4,7 +4,7 @@ //------------------------------------------------------------------------------------------------// import { LightningElement, api } from 'lwc'; -import { createLoggerService } from './loggerService'; +import { createLoggerService as createLogger, getLoggerService as getLogger } from './loggerService'; export default class Logger extends LightningElement { #loggerService; @@ -144,9 +144,4 @@ export default class Logger extends LightningElement { } } -/** - * @return {Promise} a LoggerService instance - */ -const createLogger = createLoggerService; - -export { createLogger }; +export { createLogger, getLogger }; 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 930253e60..b4c9f44d6 100644 --- a/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js +++ b/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js @@ -6,13 +6,16 @@ import { newLogEntry } from './logEntryBuilder'; import getSettings from '@salesforce/apex/ComponentLogger.getSettings'; import saveComponentLogEntries from '@salesforce/apex/ComponentLogger.saveComponentLogEntries'; +import { LoggerServiceTaskQueue } from './loggerServiceTaskQueue'; /* eslint-disable @lwc/lwc/no-dupe-class-members */ const LoggerService = class { #componentLogEntries = []; #settings; #scenario; + #taskQueue = new LoggerServiceTaskQueue(); + // TODO deprecate? Or make it async? getUserSettings() { return this.#settings; } @@ -66,71 +69,91 @@ const LoggerService = class { * on subsequent saveLog() calls */ async saveLog(saveMethodName) { - if (this.#componentLogEntries.length === 0) { - return; - } - - if (!saveMethodName && this.#settings?.defaultSaveMethodName) { - saveMethodName = this.#settings.defaultSaveMethodName; - } - - try { - const logEntriesToSave = [...this.#componentLogEntries]; - // this is an attempt to only flush the buffer for log entries that we are sending to Apex - // rather than any that could be added if the saveLog call isn't awaited properly - this.flushBuffer(); - await saveComponentLogEntries({ - componentLogEntries: logEntriesToSave, - saveMethodName - }); - } catch (error) { - if (this.#settings.isConsoleLoggingEnabled === true) { - /* eslint-disable-next-line no-console */ - console.error(error); - /* eslint-disable-next-line no-console */ - console.error(this.#componentLogEntries); + const saveLogTask = async saveMethodName => { + if (this.#componentLogEntries.length === 0) { + return; } - throw error; - } - } - async _loadSettingsFromServer() { - try { - const settings = await getSettings(); - this.#settings = Object.freeze({ - ...settings, - supportedLoggingLevels: Object.freeze(settings.supportedLoggingLevels), - userLoggingLevel: Object.freeze(settings.userLoggingLevel) - }); - } catch (error) { - /* eslint-disable-next-line no-console */ - console.error(error); - throw error; - } + if (!saveMethodName && this.#settings?.defaultSaveMethodName) { + saveMethodName = this.#settings.defaultSaveMethodName; + } + + try { + const logEntriesToSave = [...this.#componentLogEntries]; + // this is an attempt to only flush the buffer for log entries that we are sending to Apex + // rather than any that could be added if the saveLog call isn't awaited properly + this.flushBuffer(); + await saveComponentLogEntries({ + componentLogEntries: logEntriesToSave, + saveMethodName + }); + } catch (error) { + if (this.#settings.isConsoleLoggingEnabled === true) { + /* eslint-disable-next-line no-console */ + console.error(error); + /* eslint-disable-next-line no-console */ + console.error(this.#componentLogEntries); + } + throw error; + } + }; + this.#taskQueue.enqueueTask(saveLogTask, saveMethodName); + this.#taskQueue.processTaskQueue(); } - _meetsUserLoggingLevel(logEntryLoggingLevel) { - return this.#settings.isEnabled === true && this.#settings.userLoggingLevel.ordinal <= this.#settings?.supportedLoggingLevels[logEntryLoggingLevel]; + async _loadSettingsFromServer() { + const loadSettingsTask = async () => { + try { + const retrievedSettings = await getSettings(); + this.#settings = Object.freeze({ + ...retrievedSettings, + supportedLoggingLevels: Object.freeze(retrievedSettings.supportedLoggingLevels), + userLoggingLevel: Object.freeze(retrievedSettings.userLoggingLevel) + }); + } catch (error) { + /* eslint-disable-next-line no-console */ + console.error(error); + throw error; + } + }; + this.#taskQueue.enqueueTask(loadSettingsTask); + await this.#taskQueue.processTaskQueue(); } _newEntry(loggingLevel, message, originStackTraceError) { originStackTraceError = originStackTraceError ?? new Error(); - const logEntryBuilder = newLogEntry(loggingLevel, this.#settings?.isConsoleLoggingEnabled, this.#settings?.isLightningLoggerEnabled) - .parseStackTrace(originStackTraceError) - .setMessage(message) - .setScenario(this.#scenario); - if (this._meetsUserLoggingLevel(loggingLevel)) { - this.#componentLogEntries.push(logEntryBuilder.getComponentLogEntry()); - } + const logEntryBuilder = newLogEntry(loggingLevel).parseStackTrace(originStackTraceError).setMessage(message).setScenario(this.#scenario); + + const loggingLevelCheckTask = loggingLevel => { + if (this._meetsUserLoggingLevel(loggingLevel)) { + this.#componentLogEntries.push(logEntryBuilder.getComponentLogEntry()); + + if (this.#settings.isConsoleLoggingEnabled) { + logEntryBuilder.logToConsole(); + } + if (this.#settings.isLightningLoggerEnabled) { + logEntryBuilder.logToLightningLogger(); + } + } + }; + this.#taskQueue.enqueueTask(loggingLevelCheckTask, loggingLevel); return logEntryBuilder; } + + _meetsUserLoggingLevel(logEntryLoggingLevel) { + return this.#settings.isEnabled === true && this.#settings.userLoggingLevel.ordinal <= this.#settings.supportedLoggingLevels[logEntryLoggingLevel]; + } }; const createLoggerService = async function () { + return Promise.resolve(getLoggerService()); +}; + +const getLoggerService = function () { const service = new LoggerService(); - await service._loadSettingsFromServer(); + service._loadSettingsFromServer(); return service; }; -export { createLoggerService }; +export { createLoggerService, getLoggerService }; diff --git a/nebula-logger/core/main/logger-engine/lwc/logger/loggerServiceTaskQueue.js b/nebula-logger/core/main/logger-engine/lwc/logger/loggerServiceTaskQueue.js new file mode 100644 index 000000000..7cb5586b8 --- /dev/null +++ b/nebula-logger/core/main/logger-engine/lwc/logger/loggerServiceTaskQueue.js @@ -0,0 +1,30 @@ +export class LoggerServiceTaskQueue { + #isProcessing = false; + #taskQueue = []; + + enqueueTask(fn, ...args) { + this.#taskQueue.push({ fn, args }); + } + + async processTaskQueue() { + if (this.#isProcessing) { + return; + } + + this.#isProcessing = true; + + const processedTasks = []; + while (this.#taskQueue.length > 0) { + const task = this.#taskQueue.shift(); + task.output = task.fn(...task.args); + if (task.output instanceof Promise) { + task.output = await task.output; + } + processedTasks.push(task); + } + + this.#isProcessing = false; + + return processedTasks; + } +} diff --git a/nebula-logger/recipes/lwc/loggerLWCImportDemo/loggerLWCImportDemo.html b/nebula-logger/recipes/lwc/loggerLWCImportDemo/loggerLWCImportDemo.html index 52118995d..b9e06f765 100644 --- a/nebula-logger/recipes/lwc/loggerLWCImportDemo/loggerLWCImportDemo.html +++ b/nebula-logger/recipes/lwc/loggerLWCImportDemo/loggerLWCImportDemo.html @@ -8,8 +8,8 @@