Skip to content

Commit

Permalink
[WIP]
Browse files Browse the repository at this point in the history
  • Loading branch information
jongpie committed Sep 20, 2024
1 parent 1629291 commit 2f30510
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 83 deletions.
Original file line number Diff line number Diff line change
@@ -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);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand All @@ -72,8 +66,6 @@ const LogEntryBuilder = class {
*/
setMessage(message) {
this.#componentLogEntry.message = message;
this._logToConsole();
this._logToLightningLogger();
return this;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;';
Expand All @@ -262,5 +248,5 @@ export function newLogEntry(loggingLevel, isConsoleLoggingEnabled, isLightningLo

hasInitialized = true;
}
return new LogEntryBuilder(loggingLevel, isConsoleLoggingEnabled, isLightningLoggerEnabled);
return new LogEntryBuilder(loggingLevel);
}
9 changes: 2 additions & 7 deletions nebula-logger/core/main/logger-engine/lwc/logger/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -144,9 +144,4 @@ export default class Logger extends LightningElement {
}
}

/**
* @return {Promise<LoggerService>} a LoggerService instance
*/
const createLogger = createLoggerService;

export { createLogger };
export { createLogger, getLogger };
123 changes: 73 additions & 50 deletions nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 };
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<template>
<lightning-card title="Nebula Logger for Lightning Web Components - New Approach" icon-name="custom:custom19">
<div class="slds-var-m-around_medium">
✅ This component demonstrates how to use Nebula Logger by using &nbsp;<code>import { createLogger } from 'c/logger';</code> in your component's
JavaScript file. This approach was introduced in Nebula Logger v4.10.2, and is now the recommended approach.
✅ This component demonstrates how to use Nebula Logger by using &nbsp;<code>import { getLogger } from 'c/logger';</code> in your component's JavaScript
file. This approach was introduced in Nebula Logger v4.10.2, and is now the recommended approach.
</div>
<div>
<lightning-button label="Re-render LWC" onclick={handleRerenderButton}></lightning-button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
import { api, LightningElement, wire } from 'lwc';
import returnSomeString from '@salesforce/apex/LoggerLWCDemoController.returnSomeString';
import throwSomeError from '@salesforce/apex/LoggerLWCDemoController.throwSomeError';
import { createLogger } from 'c/logger';
import Logger, { getLogger } from 'c/logger';

export default class LoggerLWCImportDemo extends LightningElement {
export default class LoggerLWCImportDemo extends Logger {
someBoolean = false;

message = 'Hello, world!';
Expand All @@ -19,8 +19,8 @@ export default class LoggerLWCImportDemo extends LightningElement {
@api
logger;

async connectedCallback() {
this.logger = await createLogger();
connectedCallback() {
this.logger = getLogger();
console.log('>>> start of connectedCallback()');
try {
this.logger.error('test error entry').setField({ SomeLogEntryField__c: 'some text from loggerLWCImportDemo' });
Expand Down

0 comments on commit 2f30510

Please sign in to comment.