diff --git a/README.md b/README.md index 7c4c40506..a26961636 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ The most robust logger for Salesforce. Works with Apex, Lightning Components, Flow, Process Builder & Integrations. Designed for Salesforce admins, developers & architects. -## Unlocked Package - v4.10.1 +## Unlocked Package - v4.10.2 -[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015nYaQAI) -[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015nYaQAI) +[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000023SAGQA2) +[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000023SAGQA2) [![View Documentation](./images/btn-view-documentation.png)](https://jongpie.github.io/NebulaLogger/) -`sfdx package install --wait 20 --security-type AdminsOnly --package 04t5Y0000015nYaQAI` +`sfdx package install --wait 20 --security-type AdminsOnly --package 04t5Y0000023S8jQAE` ## Managed Package - v4.10.0 diff --git a/docs/lightning-components/LogEntryBuilder.md b/docs/lightning-components/LogEntryBuilder.md index aeac35a00..39be58ffd 100644 --- a/docs/lightning-components/LogEntryBuilder.md +++ b/docs/lightning-components/LogEntryBuilder.md @@ -5,7 +5,7 @@ **Kind**: global class - [LogEntryBuilder](#LogEntryBuilder) - - [new LogEntryBuilder(loggingLevel, shouldSave, isConsoleLoggingEnabled)](#new_LogEntryBuilder_new) + - [new LogEntryBuilder(loggingLevel, isConsoleLoggingEnabled)](#new_LogEntryBuilder_new) - [.setMessage(message)](#LogEntryBuilder+setMessage) [LogEntryBuilder](#LogEntryBuilder) - [.setRecordId(recordId)](#LogEntryBuilder+setRecordId) [LogEntryBuilder](#LogEntryBuilder) - [.setRecord(record)](#LogEntryBuilder+setRecord) [LogEntryBuilder](#LogEntryBuilder) @@ -15,7 +15,7 @@ -### new LogEntryBuilder(loggingLevel, shouldSave, isConsoleLoggingEnabled) +### new LogEntryBuilder(loggingLevel, isConsoleLoggingEnabled) Constructor used to generate each JavaScript-based log entry event This class is the JavaScript-equivalent of the Apex class `LogEntryBuilder` @@ -23,7 +23,6 @@ This class is the JavaScript-equivalent of the Apex class `LogEntryBuilder` | Param | Type | Description | | ----------------------- | -------------------- | ------------------------------------------------------------------------------- | | loggingLevel | String | The `LoggingLevel` enum to use for the builder's instance of `LogEntryEvent__e` | -| shouldSave | Boolean | Determines if the builder's instance of `LogEntryEvent__e` should be saved | | isConsoleLoggingEnabled | Boolean | Determines if `console.log()` methods are execute | @@ -32,7 +31,7 @@ This class is the JavaScript-equivalent of the Apex class `LogEntryBuilder` Sets the log entry event's message field -**Kind**: instance method of [LogEntryBuilder](#LogEntryBuilder) +**Kind**: instance method of [LogEntryBuilder](#LogEntryBuilder) **Returns**: [LogEntryBuilder](#LogEntryBuilder) - The same instance of `LogEntryBuilder`, useful for chaining methods | Param | Type | Description | @@ -45,7 +44,7 @@ Sets the log entry event's message field Sets the log entry event's record fields -**Kind**: instance method of [LogEntryBuilder](#LogEntryBuilder) +**Kind**: instance method of [LogEntryBuilder](#LogEntryBuilder) **Returns**: [LogEntryBuilder](#LogEntryBuilder) - The same instance of `LogEntryBuilder`, useful for chaining methods | Param | Type | Description | @@ -58,7 +57,7 @@ Sets the log entry event's record fields Sets the log entry event's record fields -**Kind**: instance method of [LogEntryBuilder](#LogEntryBuilder) +**Kind**: instance method of [LogEntryBuilder](#LogEntryBuilder) **Returns**: [LogEntryBuilder](#LogEntryBuilder) - The same instance of `LogEntryBuilder`, useful for chaining methods | Param | Type | Description | @@ -71,7 +70,7 @@ Sets the log entry event's record fields Sets the log entry event's exception fields -**Kind**: instance method of [LogEntryBuilder](#LogEntryBuilder) +**Kind**: instance method of [LogEntryBuilder](#LogEntryBuilder) **Returns**: [LogEntryBuilder](#LogEntryBuilder) - The same instance of `LogEntryBuilder`, useful for chaining methods | Param | Type | Description | @@ -84,7 +83,7 @@ Sets the log entry event's exception fields Appends the tag to the existing list of tags -**Kind**: instance method of [LogEntryBuilder](#LogEntryBuilder) +**Kind**: instance method of [LogEntryBuilder](#LogEntryBuilder) **Returns**: [LogEntryBuilder](#LogEntryBuilder) - The same instance of `LogEntryBuilder`, useful for chaining methods | Param | Type | Description | @@ -97,7 +96,7 @@ Appends the tag to the existing list of tags Appends the tag to the existing list of tags -**Kind**: instance method of [LogEntryBuilder](#LogEntryBuilder) +**Kind**: instance method of [LogEntryBuilder](#LogEntryBuilder) **Returns**: [LogEntryBuilder](#LogEntryBuilder) - The same instance of `LogEntryBuilder`, useful for chaining methods | Param | Type | Description | diff --git a/docs/lightning-components/Logger.md b/docs/lightning-components/Logger.md index 7b38663e5..207d32ec3 100644 --- a/docs/lightning-components/Logger.md +++ b/docs/lightning-components/Logger.md @@ -1,12 +1,12 @@ ## Functions
-
getUserSettings() ComponentLogger.ComponentLoggerSettings
-

Returns information about the current user's settings, stored in LoggerSettings__c

+
getUserSettings(parameters) Promise.<ComponentLogger.ComponentLoggerSettings>
+

Returns read-only information about the current user's settings, stored in LoggerSettings__c

-
setScenario(scenario)
+
setScenario(scenario) Array.<Promise>

Sets the scenario name for the current transaction - this is stored in LogEntryEvent__e.Scenario__c - and Log__c.TransactionScenario__c, and can be used to filter & group logs

+ and Log__c.Scenario__c, and can be used to filter & group logs

error(message) LogEntryBuilder

Creates a new log entry with logging level == LoggingLevel.ERROR

@@ -32,31 +32,39 @@
getBufferSize() Integer

Returns the number of entries that have been generated but not yet saved

-
flushBuffer()
+
flushBuffer() Promise.<void>

Discards any entries that have been generated but not yet saved

saveLog(saveMethod)
-

Saves any entries in Logger's buffer, using the specified save method for only this call. - All subsequent calls to saveLog() will use the transaction save method.

+

Saves any entries in Logger's buffer, using the specified save method for only this call + All subsequent calls to saveLog() will use the transaction save method

+
createLogger() LoggerService
+
-## getUserSettings() ComponentLogger.ComponentLoggerSettings +## getUserSettings(parameters) Promise.<ComponentLogger.ComponentLoggerSettings> -Returns information about the current user's settings, stored in `LoggerSettings__c` +Returns **read-only** information about the current user's settings, stored in `LoggerSettings__c` + +**Kind**: global function +**Returns**: Promise.<ComponentLogger.ComponentLoggerSettings> - The current user's instance of the Apex class `ComponentLogger.ComponentLoggerSettings` + +| Param | Type | Description | +| ---------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| parameters | Object | Object used to provide control over how user settings are retrieved. Currently, only the property `forceReload` is used. | -**Kind**: global function -**Returns**: ComponentLogger.ComponentLoggerSettings - The current user's instance of the Apex class `ComponentLogger.ComponentLoggerSettings` -## setScenario(scenario) +## setScenario(scenario) Array.<Promise> Sets the scenario name for the current transaction - this is stored in `LogEntryEvent__e.Scenario__c` and `Log__c.Scenario__c`, and can be used to filter & group logs -**Kind**: global function +**Kind**: global function +**Returns**: Array.<Promise> - A list of promises that be resolved before all scenarios are set | Param | Type | Description | | -------- | ------------------- | ------------------------------------------------------ | @@ -68,7 +76,7 @@ and `Log__c.Scenario__c`, and can be used to filter & group logs Creates a new log entry with logging level == `LoggingLevel.ERROR` -**Kind**: global function +**Kind**: global function **Returns**: LogEntryBuilder - The new entry's instance of `LogEntryEventBuilder`, useful for chaining methods | Param | Type | Description | @@ -81,7 +89,7 @@ Creates a new log entry with logging level == `LoggingLevel.ERROR` Creates a new log entry with logging level == `LoggingLevel.WARN` -**Kind**: global function +**Kind**: global function **Returns**: LogEntryBuilder - The new entry's instance of `LogEntryEventBuilder`, useful for chaining methods | Param | Type | Description | @@ -94,7 +102,7 @@ Creates a new log entry with logging level == `LoggingLevel.WARN` Creates a new log entry with logging level == `LoggingLevel.INFO` -**Kind**: global function +**Kind**: global function **Returns**: LogEntryBuilder - The new entry's instance of `LogEntryEventBuilder`, useful for chaining methods | Param | Type | Description | @@ -107,7 +115,7 @@ Creates a new log entry with logging level == `LoggingLevel.INFO` Creates a new log entry with logging level == `LoggingLevel.DEBUG` -**Kind**: global function +**Kind**: global function **Returns**: LogEntryBuilder - The new entry's instance of `LogEntryEventBuilder`, useful for chaining methods | Param | Type | Description | @@ -120,7 +128,7 @@ Creates a new log entry with logging level == `LoggingLevel.DEBUG` Creates a new log entry with logging level == `LoggingLevel.FINE` -**Kind**: global function +**Kind**: global function **Returns**: LogEntryBuilder - The new entry's instance of `LogEntryEventBuilder`, useful for chaining methods | Param | Type | Description | @@ -133,7 +141,7 @@ Creates a new log entry with logging level == `LoggingLevel.FINE` Creates a new log entry with logging level == `LoggingLevel.FINER` -**Kind**: global function +**Kind**: global function **Returns**: LogEntryBuilder - The new entry's instance of `LogEntryEventBuilder`, useful for chaining methods | Param | Type | Description | @@ -146,7 +154,7 @@ Creates a new log entry with logging level == `LoggingLevel.FINER` Creates a new log entry with logging level == `LoggingLevel.FINEST` -**Kind**: global function +**Kind**: global function **Returns**: LogEntryBuilder - The new entry's instance of `LogEntryEventBuilder`, useful for chaining methods | Param | Type | Description | @@ -159,24 +167,32 @@ Creates a new log entry with logging level == `LoggingLevel.FINEST` Returns the number of entries that have been generated but not yet saved -**Kind**: global function -**Returns**: Integer - The buffer's current size +**Kind**: global function +**Returns**: Integer - The buffer's current size -## flushBuffer() +## flushBuffer() Promise.<void> Discards any entries that have been generated but not yet saved -**Kind**: global function +**Kind**: global function +**Returns**: Promise.<void> - A promise to clear the entries ## saveLog(saveMethod) -Saves any entries in Logger's buffer, using the specified save method for only this call. -All subsequent calls to saveLog() will use the transaction save method. +Saves any entries in Logger's buffer, using the specified save method for only this call +All subsequent calls to saveLog() will use the transaction save method **Kind**: global function -| Param | Type | Description | -| ---------- | ------------------- | ------------------------------------------------------------------------- | -| saveMethod | String | The enum value of Logger.SaveMethod to use for this specific save action. | +| Param | Type | Description | +| ---------- | ------------------- | ------------------------------------------------------------------------ | +| saveMethod | String | The enum value of Logger.SaveMethod to use for this specific save action | + + + +## createLogger() LoggerService + +**Kind**: global function +**Returns**: LoggerService - a LoggerService instance diff --git a/nebula-logger/core/main/logger-engine/classes/Logger.cls b/nebula-logger/core/main/logger-engine/classes/Logger.cls index 7a655c79f..32bf16b89 100644 --- a/nebula-logger/core/main/logger-engine/classes/Logger.cls +++ b/nebula-logger/core/main/logger-engine/classes/Logger.cls @@ -15,7 +15,7 @@ global with sharing class Logger { // There's no reliable way to get the version number dynamically in Apex @TestVisible - private static final String CURRENT_VERSION_NUMBER = 'v4.10.1'; + private static final String CURRENT_VERSION_NUMBER = 'v4.10.2'; private static final System.LoggingLevel FALLBACK_LOGGING_LEVEL = System.LoggingLevel.DEBUG; private static final Set IGNORED_APEX_CLASSES = initializeIgnoredApexClasses(); private static final List LOG_ENTRIES_BUFFER = new List(); diff --git a/nebula-logger/core/main/logger-engine/lwc/logger/__tests__/logger.test.js b/nebula-logger/core/main/logger-engine/lwc/logger/__tests__/logger.test.js index 187a3875e..14949ab82 100644 --- a/nebula-logger/core/main/logger-engine/lwc/logger/__tests__/logger.test.js +++ b/nebula-logger/core/main/logger-engine/lwc/logger/__tests__/logger.test.js @@ -1,155 +1,621 @@ import { createElement } from 'lwc'; +// Recommended approach +import { createLogger } from 'c/logger'; +// Legacy approach import Logger from 'c/logger'; import getSettings from '@salesforce/apex/ComponentLogger.getSettings'; const MOCK_GET_SETTINGS = require('./data/getLoggerSettings.json'); +const flushPromises = async () => { + await new Promise(process.nextTick); +}; + jest.mock( '@salesforce/apex/ComponentLogger.getSettings', () => { - const { createApexTestWireAdapter } = require('@salesforce/sfdx-lwc-jest'); return { - default: createApexTestWireAdapter(jest.fn()) + default: jest.fn() }; }, { virtual: true } ); -describe('Logger lwc tests', () => { +describe('logger lwc import tests', () => { + afterEach(() => { + createLogger().flushBuffer(); + jest.clearAllMocks(); + }); + + it('returns user settings when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + + const userSettings = await logger.getUserSettings(); + + expect(userSettings.defaultSaveMethod).toEqual('EVENT_BUS'); + expect(userSettings.isEnabled).toEqual(true); + expect(userSettings.isConsoleLoggingEnabled).toEqual(true); + }); + + it('sets a log scenario on all entries when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + await logger.getUserSettings(); + const scenario = 'some scenario'; + const message = 'some message'; + const firstLogEntry = await logger.finest(message); + await flushPromises(); + expect(firstLogEntry.scenario).toBeUndefined(); + expect(logger.getBufferSize()).toEqual(1); + const secondLogEntry = logger.info(message); + await flushPromises(); + expect(secondLogEntry.scenario).toBeUndefined(); + expect(logger.getBufferSize()).toEqual(2); + + await logger.setScenario(scenario); + + expect(firstLogEntry.scenario).toEqual(scenario); + expect(secondLogEntry.scenario).toEqual(scenario); + }); + + it('logs an ERROR entry when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const message = 'component log entry with loggingLevel ERROR'; + + const logEntry = logger.error(message); + + await flushPromises(); + expect(logger.getBufferSize()).toEqual(1); + expect(logEntry.loggingLevel).toEqual('ERROR'); + expect(logEntry.message).toEqual(message); + }); + + it('logs a WARN entry when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const message = 'component log entry with loggingLevel WARN'; + + const logEntry = logger.warn(message); + + await flushPromises(); + expect(logger.getBufferSize()).toEqual(1); + expect(logEntry.loggingLevel).toEqual('WARN'); + expect(logEntry.message).toEqual(message); + }); + + it('logs an INFO entry when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const message = 'component log entry with loggingLevel INFO'; + + const logEntry = logger.info(message); + + await flushPromises(); + expect(logger.getBufferSize()).toEqual(1); + expect(logEntry.loggingLevel).toEqual('INFO'); + expect(logEntry.message).toEqual(message); + }); + + it('logs a DEBUG entry when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const message = 'component log entry with loggingLevel DEBUG'; + + const logEntry = logger.debug(message); + + await flushPromises(); + expect(logger.getBufferSize()).toEqual(1); + expect(logEntry.loggingLevel).toEqual('DEBUG'); + expect(logEntry.message).toEqual(message); + }); + + it('logs a FINE entry when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const message = 'component log entry with loggingLevel FINE'; + + const logEntry = logger.fine(message); + + await flushPromises(); + expect(logger.getBufferSize()).toEqual(1); + expect(logEntry.loggingLevel).toEqual('FINE'); + expect(logEntry.message).toEqual(message); + }); + + it('logs a FINER entry when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const message = 'component log entry with loggingLevel FINER'; + + const logEntry = logger.finer(message); + + await flushPromises(); + expect(logger.getBufferSize()).toEqual(1); + expect(logEntry.loggingLevel).toEqual('FINER'); + expect(logEntry.message).toEqual(message); + }); + + it('logs a FINEST entry when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const message = 'component log entry with loggingLevel FINEST'; + + const logEntry = logger.finest(message); + + await flushPromises(); + expect(logger.getBufferSize()).toEqual(1); + expect(logEntry.loggingLevel).toEqual('FINEST'); + expect(logEntry.message).toEqual(message); + }); + + it('sets recordId when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + await logger.getUserSettings(); + const logEntry = logger.info('example log entry'); + expect(logEntry.recordId).toBeFalsy(); + const mockUserId = '0052F000008yLcEQAU'; + + logEntry.setRecordId(mockUserId); + + expect(logEntry.recordId).toEqual(mockUserId); + }); + + it('sets record when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + await logger.getUserSettings(); + const logEntry = logger.info('example log entry'); + expect(logEntry.record).toBeFalsy(); + const mockUserRecord = { Id: '0052F000008yLcEQAU', FirstName: 'Jonathan', LastName: 'Gillespie' }; + + logEntry.setRecord(mockUserRecord); + + expect(logEntry.record).toEqual(mockUserRecord); + }); + + it('sets JavaScript error details when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + await logger.getUserSettings(); + const logEntry = logger.info('example log entry'); + expect(logEntry.error).toBeFalsy(); + const error = new TypeError('oops'); + expect(error).toBeTruthy(); + expect(error.message).toBeTruthy(); + expect(error.stack).toBeTruthy(); + + logEntry.setError(error); + + expect(logEntry.error.message).toEqual(error.message); + expect(logEntry.error.stack).toEqual(error.stack); + expect(logEntry.error.type).toEqual('JavaScript.TypeError'); + }); + + it('sets Apex error details when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const logEntry = logger.info('example log entry'); + expect(logEntry.error).toBeFalsy(); + const error = { + body: { + exceptionType: 'System.DmlException', + message: 'Some Apex error, oh no!', + stackTrace: 'Class.SomeApexClass.runSomeMethod: line 314, column 42' + } + }; + expect(error).toBeTruthy(); + expect(error.body.exceptionType).toBeTruthy(); + expect(error.body.message).toBeTruthy(); + expect(error.body.stackTrace).toBeTruthy(); + + logEntry.setError(error); + + expect(logEntry.error.message).toEqual(error.body.message); + expect(logEntry.error.stack).toEqual(error.body.stackTrace); + expect(logEntry.error.type).toEqual(error.body.exceptionType); + }); + + it('adds tags when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const logEntry = logger.info('example log entry'); + expect(logEntry.recordId).toBeFalsy(); + const mockTags = ['first tag', 'second tag', 'third tag']; + + logEntry.addTags(mockTags); + + expect(logEntry.tags.length).toEqual(mockTags.length); + }); + + it('deduplicates tags when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const logEntry = logger.info('example log entry'); + expect(logEntry.recordId).toBeFalsy(); + const mockTags = ['duplicate tag', 'duplicate tag']; + expect(mockTags.length).toEqual(2); + expect(new Set(mockTags).size).toEqual(1); + + for (let i = 0; i < mockTags.length; i++) { + logEntry.addTag(mockTags[i]); + } + + expect(logEntry.tags.length).toEqual(1); + }); + + it('still works for ERROR logging level when disabled when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS, isEnabled: false }); + const settings = await logger.getUserSettings({ forceReload: true }); + expect(settings.isEnabled).toEqual(false); + const error = new TypeError('oops'); + + const logEntry = logger + .error('example ERROR log entry') + .setMessage('some message') + .setRecordId('some_record_Id') + .setRecord({ Id: 'some_record_Id' }) + .setError(error) + .addTag('a tag') + .addTags(['a second tag', 'a third tag']); + + await flushPromises(); + expect(logger.getBufferSize()).toEqual(0); + expect(logEntry.loggingLevel).toEqual('ERROR'); + expect(logEntry.recordId).toEqual('some_record_Id'); + expect(logEntry.record).toEqual({ Id: 'some_record_Id' }); + expect(logEntry.error).toBeTruthy(); + expect(logEntry.stack).toBeTruthy(); + expect(logEntry.error.message).toEqual(error.message); + expect(logEntry.error.stack).toEqual(error.stack); + expect(logEntry.timestamp).toBeTruthy(); + expect(logEntry.tags).toEqual(['a tag', 'a second tag', 'a third tag']); + }); + + it('still works for WARN logging level when disabled when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS, isEnabled: false }); + const settings = await logger.getUserSettings({ forceReload: true }); + expect(settings.isEnabled).toEqual(false); + const error = new TypeError('oops'); + + const logEntry = logger + .warn('example WARN log entry') + .setMessage('some message') + .setRecordId('some_record_Id') + .setRecord({ Id: 'some_record_Id' }) + .setError(error) + .addTag('a tag') + .addTags(['a second tag', 'a third tag']); + + await flushPromises(); + expect(logger.getBufferSize()).toEqual(0); + expect(logEntry.loggingLevel).toEqual('WARN'); + expect(logEntry.recordId).toEqual('some_record_Id'); + expect(logEntry.record).toEqual({ Id: 'some_record_Id' }); + expect(logEntry.error).toBeTruthy(); + expect(logEntry.stack).toBeTruthy(); + expect(logEntry.error.message).toEqual(error.message); + expect(logEntry.error.stack).toEqual(error.stack); + expect(logEntry.timestamp).toBeTruthy(); + expect(logEntry.tags).toEqual(['a tag', 'a second tag', 'a third tag']); + }); + + it('still works for INFO logging level when disabled when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS, isEnabled: false }); + const settings = await logger.getUserSettings({ forceReload: true }); + expect(settings.isEnabled).toEqual(false); + const error = new TypeError('oops'); + + const logEntry = logger + .info('example INFO log entry') + .setMessage('some message') + .setRecordId('some_record_Id') + .setRecord({ Id: 'some_record_Id' }) + .setError(error) + .addTag('a tag') + .addTags(['a second tag', 'a third tag']); + + await flushPromises(); + expect(logger.getBufferSize()).toEqual(0); + expect(logEntry.loggingLevel).toEqual('INFO'); + expect(logEntry.recordId).toEqual('some_record_Id'); + expect(logEntry.record).toEqual({ Id: 'some_record_Id' }); + expect(logEntry.error).toBeTruthy(); + expect(logEntry.stack).toBeTruthy(); + expect(logEntry.error.message).toEqual(error.message); + expect(logEntry.error.stack).toEqual(error.stack); + expect(logEntry.timestamp).toBeTruthy(); + expect(logEntry.tags).toEqual(['a tag', 'a second tag', 'a third tag']); + }); + + it('still works for DEBUG logging level when disabled when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS, isEnabled: false }); + const settings = await logger.getUserSettings({ forceReload: true }); + expect(settings.isEnabled).toEqual(false); + const error = new TypeError('oops'); + + const logEntry = logger + .debug('example DEBUG log entry') + .setMessage('some message') + .setRecordId('some_record_Id') + .setRecord({ Id: 'some_record_Id' }) + .setError(error) + .addTag('a tag') + .addTags(['a second tag', 'a third tag']); + + await flushPromises(); + expect(logger.getBufferSize()).toEqual(0); + expect(logEntry.loggingLevel).toEqual('DEBUG'); + expect(logEntry.recordId).toEqual('some_record_Id'); + expect(logEntry.record).toEqual({ Id: 'some_record_Id' }); + expect(logEntry.error).toBeTruthy(); + expect(logEntry.stack).toBeTruthy(); + expect(logEntry.error.message).toEqual(error.message); + expect(logEntry.error.stack).toEqual(error.stack); + expect(logEntry.timestamp).toBeTruthy(); + expect(logEntry.tags).toEqual(['a tag', 'a second tag', 'a third tag']); + }); + + it('still works for FINE logging level when disabled when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS, isEnabled: false }); + const settings = await logger.getUserSettings({ forceReload: true }); + expect(settings.isEnabled).toEqual(false); + const error = new TypeError('oops'); + + const logEntry = logger + .fine('example FINE log entry') + .setMessage('some message') + .setRecordId('some_record_Id') + .setRecord({ Id: 'some_record_Id' }) + .setError(error) + .addTag('a tag') + .addTags(['a second tag', 'a third tag']); + + await flushPromises(); + expect(logger.getBufferSize()).toEqual(0); + expect(logEntry.loggingLevel).toEqual('FINE'); + expect(logEntry.recordId).toEqual('some_record_Id'); + expect(logEntry.record).toEqual({ Id: 'some_record_Id' }); + expect(logEntry.error).toBeTruthy(); + expect(logEntry.stack).toBeTruthy(); + expect(logEntry.error.message).toEqual(error.message); + expect(logEntry.error.stack).toEqual(error.stack); + expect(logEntry.timestamp).toBeTruthy(); + expect(logEntry.tags).toEqual(['a tag', 'a second tag', 'a third tag']); + }); + + it('still works for FINER logging level when disabled when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS, isEnabled: false }); + const settings = await logger.getUserSettings({ forceReload: true }); + expect(settings.isEnabled).toEqual(false); + const error = new TypeError('oops'); + + const logEntry = logger + .finer('example FINER log entry') + .setMessage('some message') + .setRecordId('some_record_Id') + .setRecord({ Id: 'some_record_Id' }) + .setError(error) + .addTag('a tag') + .addTags(['a second tag', 'a third tag']); + + await flushPromises(); + expect(logger.getBufferSize()).toEqual(0); + expect(logEntry.loggingLevel).toEqual('FINER'); + expect(logEntry.recordId).toEqual('some_record_Id'); + expect(logEntry.record).toEqual({ Id: 'some_record_Id' }); + expect(logEntry.error).toBeTruthy(); + expect(logEntry.stack).toBeTruthy(); + expect(logEntry.error.message).toEqual(error.message); + expect(logEntry.error.stack).toEqual(error.stack); + expect(logEntry.timestamp).toBeTruthy(); + expect(logEntry.tags).toEqual(['a tag', 'a second tag', 'a third tag']); + }); + + it('still works for FINEST logging level when disabled when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS, isEnabled: false }); + const settings = await logger.getUserSettings({ forceReload: true }); + expect(settings.isEnabled).toEqual(false); + const error = new TypeError('oops'); + + const logEntry = logger + .finest('example FINEST log entry') + .setMessage('some message') + .setRecordId('some_record_Id') + .setRecord({ Id: 'some_record_Id' }) + .setError(error) + .addTag('a tag') + .addTags(['a second tag', 'a third tag']); + + await flushPromises(); + expect(logger.getBufferSize()).toEqual(0); + expect(logEntry.loggingLevel).toEqual('FINEST'); + expect(logEntry.recordId).toEqual('some_record_Id'); + expect(logEntry.record).toEqual({ Id: 'some_record_Id' }); + expect(logEntry.error).toBeTruthy(); + expect(logEntry.stack).toBeTruthy(); + expect(logEntry.error.message).toEqual(error.message); + expect(logEntry.error.stack).toEqual(error.stack); + expect(logEntry.timestamp).toBeTruthy(); + expect(logEntry.tags).toEqual(['a tag', 'a second tag', 'a third tag']); + }); + + it('flushes buffer when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + await logger.getUserSettings({ forceReload: true }); + const numberOfLogEntries = 3; + for (let i = 0; i < numberOfLogEntries; i++) { + logger.info('entry number: ' + i); + } + await flushPromises(); + expect(logger.getBufferSize()).toEqual(numberOfLogEntries); + + await logger.flushBuffer(); + + expect(logger.getBufferSize()).toEqual(0); + }); + + it('saves log entries and flushes buffer when using recommended import approach', async () => { + const logger = createLogger(); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + await logger.getUserSettings({ forceReload: true }); + logger.info('example INFO log entry'); + logger.debug('example DEBUG log entry'); + await flushPromises(); + expect(logger.getBufferSize()).toBe(2); + + logger.saveLog(); + + await flushPromises(); + expect(logger.getBufferSize()).toBe(0); + }); +}); + +describe('logger lwc legacy markup tests', () => { afterEach(() => { while (document.body.firstChild) { document.body.removeChild(document.body.firstChild); } + createLogger().flushBuffer(); jest.clearAllMocks(); }); - it('returns user settings', async () => { + it('returns user settings when using deprecated markup approach', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); - const userSettings = logger.getUserSettings(); + const userSettings = await logger.getUserSettings(); expect(userSettings.defaultSaveMethod).toEqual('EVENT_BUS'); expect(userSettings.isEnabled).toEqual(true); expect(userSettings.isConsoleLoggingEnabled).toEqual(true); }); - it('sets a log scenario on all entries', async () => { + it('sets a log scenario on all entries when using deprecated markup approach', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); - logger.getUserSettings().isEnabled = true; - const scenario = 'some scenario'; + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); const message = 'some message'; - const firstLogEntry = logger.finest(message); + const firstLogEntry = await logger.finest(message); expect(firstLogEntry.scenario).toBeUndefined(); expect(logger.getBufferSize()).toEqual(1); - const secondLogEntry = logger.info(message); + const secondLogEntry = await logger.info(message); expect(secondLogEntry.scenario).toBeUndefined(); expect(logger.getBufferSize()).toEqual(2); + const scenario = 'some scenario'; logger.setScenario(scenario); expect(firstLogEntry.scenario).toEqual(scenario); expect(secondLogEntry.scenario).toEqual(scenario); }); - it('logs an ERROR entry', async () => { + it('logs an ERROR entry when using deprecated markup approach', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); const message = 'component log entry with loggingLevel ERROR'; - const logEntry = logger.error(message); + const logEntry = await logger.error(message); expect(logger.getBufferSize()).toEqual(1); expect(logEntry.loggingLevel).toEqual('ERROR'); expect(logEntry.message).toEqual(message); }); - it('logs a WARN entry', async () => { + it('logs a WARN entry when using deprecated markup approach', async () => { const logger = createElement('c-logger', { is: Logger }); - getSettings.emit({ ...MOCK_GET_SETTINGS }); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); const message = 'component log entry with loggingLevel WARN'; - const logEntry = logger.warn(message); + const logEntry = await logger.warn(message); expect(logger.getBufferSize()).toEqual(1); expect(logEntry.loggingLevel).toEqual('WARN'); expect(logEntry.message).toEqual(message); }); - it('logs an INFO entry', async () => { + it('logs an INFO entry when using deprecated markup approach', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); const message = 'component log entry with loggingLevel INFO'; - const logEntry = logger.info(message); + const logEntry = await logger.info(message); expect(logger.getBufferSize()).toEqual(1); expect(logEntry.loggingLevel).toEqual('INFO'); expect(logEntry.message).toEqual(message); }); - it('logs a DEBUG entry', async () => { + it('logs a DEBUG entry when using deprecated markup approach', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); const message = 'component log entry with loggingLevel DEBUG'; - const logEntry = logger.debug(message); + const logEntry = await logger.debug(message); expect(logger.getBufferSize()).toEqual(1); expect(logEntry.loggingLevel).toEqual('DEBUG'); expect(logEntry.message).toEqual(message); }); - it('logs a FINE entry', async () => { + it('logs a FINE entry when using deprecated markup approach', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); const message = 'component log entry with loggingLevel FINE'; - const logEntry = logger.fine(message); + const logEntry = await logger.fine(message); expect(logger.getBufferSize()).toEqual(1); expect(logEntry.loggingLevel).toEqual('FINE'); expect(logEntry.message).toEqual(message); }); - it('logs a FINER entry', async () => { + it('logs a FINER entry when using deprecated markup approach', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); const message = 'component log entry with loggingLevel FINER'; - const logEntry = logger.finer(message); + const logEntry = await logger.finer(message); expect(logger.getBufferSize()).toEqual(1); expect(logEntry.loggingLevel).toEqual('FINER'); expect(logEntry.message).toEqual(message); }); - it('logs a FINEST entry', async () => { + it('logs a FINEST entry when using deprecated markup approach', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); const message = 'component log entry with loggingLevel FINEST'; - const logEntry = logger.finest(message); + const logEntry = await logger.finest(message); expect(logger.getBufferSize()).toEqual(1); expect(logEntry.loggingLevel).toEqual('FINEST'); expect(logEntry.message).toEqual(message); }); - it('sets recordId', async () => { + it('sets recordId when using deprecated markup approach', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); - const logEntry = logger.info('example log entry'); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const logEntry = await logger.info('example log entry'); expect(logEntry.recordId).toBeFalsy(); const mockUserId = '0052F000008yLcEQAU'; @@ -158,11 +624,11 @@ describe('Logger lwc tests', () => { expect(logEntry.recordId).toEqual(mockUserId); }); - it('sets record', async () => { + it('sets record when using deprecated markup approach', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); - const logEntry = logger.info('example log entry'); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const logEntry = await logger.info('example log entry'); expect(logEntry.record).toBeFalsy(); const mockUserRecord = { Id: '0052F000008yLcEQAU', FirstName: 'Jonathan', LastName: 'Gillespie' }; @@ -171,11 +637,11 @@ describe('Logger lwc tests', () => { expect(logEntry.record).toEqual(mockUserRecord); }); - it('sets JavaScript error details', async () => { + it('sets JavaScript error details when using deprecated markup approach', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); - const logEntry = logger.info('example log entry'); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const logEntry = await logger.info('example log entry'); expect(logEntry.error).toBeFalsy(); const error = new TypeError('oops'); expect(error).toBeTruthy(); @@ -189,11 +655,11 @@ describe('Logger lwc tests', () => { expect(logEntry.error.type).toEqual('JavaScript.TypeError'); }); - it('sets Apex error details', async () => { + it('sets Apex error details when using deprecated markup approach', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); - const logEntry = logger.info('example log entry'); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const logEntry = await logger.info('example log entry'); expect(logEntry.error).toBeFalsy(); const error = { body: { @@ -214,11 +680,11 @@ describe('Logger lwc tests', () => { expect(logEntry.error.type).toEqual(error.body.exceptionType); }); - it('adds tags', async () => { + it('adds tags when using deprecated markup approach', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); - const logEntry = logger.info('example log entry'); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const logEntry = await logger.info('example log entry'); expect(logEntry.recordId).toBeFalsy(); const mockTags = ['first tag', 'second tag', 'third tag']; @@ -227,11 +693,11 @@ describe('Logger lwc tests', () => { expect(logEntry.tags.length).toEqual(mockTags.length); }); - it('deduplicates tags', async () => { + it('deduplicates tags when using deprecated markup approach', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); - const logEntry = logger.info('example log entry'); + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); + const logEntry = await logger.info('example log entry'); expect(logEntry.recordId).toBeFalsy(); const mockTags = ['duplicate tag', 'duplicate tag']; expect(mockTags.length).toEqual(2); @@ -244,220 +710,243 @@ describe('Logger lwc tests', () => { expect(logEntry.tags.length).toEqual(1); }); - it('still works for ERROR logging level when disabled', async () => { + it('still works for ERROR logging level when disabled when using deprecated markup approach', async () => { + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS, isEnabled: false }); const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); - logger.getUserSettings().isEnabled = false; + const settings = await logger.getUserSettings({ forceReload: true }); + expect(settings.isEnabled).toEqual(false); + const error = new TypeError('oops'); const logEntry = logger .error('example ERROR log entry') .setMessage('some message') .setRecordId('some_record_Id') .setRecord({ Id: 'some_record_Id' }) - .setError(new TypeError('oops')) + .setError(error) .addTag('a tag') .addTags(['a second tag', 'a third tag']); expect(logger.getBufferSize()).toEqual(0); - expect(logEntry.loggingLevel).toEqual(undefined); - expect(logEntry.shouldSave).toEqual(false); - expect(logEntry.recordId).toEqual(undefined); - expect(logEntry.record).toEqual(undefined); - expect(logEntry.error).toEqual(undefined); - expect(logEntry.stack).toEqual(undefined); - expect(logEntry.timestamp).toEqual(undefined); - expect(logEntry.tags).toEqual(undefined); + expect(logEntry.loggingLevel).toEqual('ERROR'); + expect(logEntry.recordId).toEqual('some_record_Id'); + expect(logEntry.record).toEqual({ Id: 'some_record_Id' }); + expect(logEntry.error).toBeTruthy(); + expect(logEntry.stack).toBeTruthy(); + expect(logEntry.error.message).toEqual(error.message); + expect(logEntry.error.stack).toEqual(error.stack); + expect(logEntry.timestamp).toBeTruthy(); + expect(logEntry.tags).toEqual(['a tag', 'a second tag', 'a third tag']); }); - it('still works for WARN logging level when disabled', async () => { + it('still works for WARN logging level when disabled when using deprecated markup approach', async () => { + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS, isEnabled: false }); const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); - logger.getUserSettings().isEnabled = false; + const settings = await logger.getUserSettings({ forceReload: true }); + expect(settings.isEnabled).toEqual(false); + const error = new TypeError('oops'); const logEntry = logger .warn('example WARN log entry') .setMessage('some message') .setRecordId('some_record_Id') .setRecord({ Id: 'some_record_Id' }) - .setError(new TypeError('oops')) + .setError(error) .addTag('a tag') .addTags(['a second tag', 'a third tag']); expect(logger.getBufferSize()).toEqual(0); - expect(logger.getBufferSize()).toEqual(0); - expect(logEntry.loggingLevel).toEqual(undefined); - expect(logEntry.shouldSave).toEqual(false); - expect(logEntry.recordId).toEqual(undefined); - expect(logEntry.record).toEqual(undefined); - expect(logEntry.error).toEqual(undefined); - expect(logEntry.stack).toEqual(undefined); - expect(logEntry.timestamp).toEqual(undefined); - expect(logEntry.tags).toEqual(undefined); + expect(logEntry.loggingLevel).toEqual('WARN'); + expect(logEntry.recordId).toEqual('some_record_Id'); + expect(logEntry.record).toEqual({ Id: 'some_record_Id' }); + expect(logEntry.error).toBeTruthy(); + expect(logEntry.stack).toBeTruthy(); + expect(logEntry.error.message).toEqual(error.message); + expect(logEntry.error.stack).toEqual(error.stack); + expect(logEntry.timestamp).toBeTruthy(); + expect(logEntry.tags).toEqual(['a tag', 'a second tag', 'a third tag']); }); - it('still works for INFO logging level when disabled', async () => { + it('still works for INFO logging level when disabled when using deprecated markup approach', async () => { + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS, isEnabled: false }); const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); - logger.getUserSettings().isEnabled = false; + const settings = await logger.getUserSettings({ forceReload: true }); + expect(settings.isEnabled).toEqual(false); + const error = new TypeError('oops'); const logEntry = logger .info('example INFO log entry') .setMessage('some message') .setRecordId('some_record_Id') .setRecord({ Id: 'some_record_Id' }) - .setError(new TypeError('oops')) + .setError(error) .addTag('a tag') .addTags(['a second tag', 'a third tag']); expect(logger.getBufferSize()).toEqual(0); - expect(logger.getBufferSize()).toEqual(0); - expect(logEntry.loggingLevel).toEqual(undefined); - expect(logEntry.shouldSave).toEqual(false); - expect(logEntry.recordId).toEqual(undefined); - expect(logEntry.record).toEqual(undefined); - expect(logEntry.error).toEqual(undefined); - expect(logEntry.stack).toEqual(undefined); - expect(logEntry.timestamp).toEqual(undefined); - expect(logEntry.tags).toEqual(undefined); + expect(logEntry.loggingLevel).toEqual('INFO'); + expect(logEntry.recordId).toEqual('some_record_Id'); + expect(logEntry.record).toEqual({ Id: 'some_record_Id' }); + expect(logEntry.error).toBeTruthy(); + expect(logEntry.stack).toBeTruthy(); + expect(logEntry.error.message).toEqual(error.message); + expect(logEntry.error.stack).toEqual(error.stack); + expect(logEntry.timestamp).toBeTruthy(); + expect(logEntry.tags).toEqual(['a tag', 'a second tag', 'a third tag']); }); - it('still works for DEBUG logging level when disabled', async () => { + it('still works for DEBUG logging level when disabled when using deprecated markup approach', async () => { + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS, isEnabled: false }); const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); - logger.getUserSettings().isEnabled = false; + const settings = await logger.getUserSettings({ forceReload: true }); + expect(settings.isEnabled).toEqual(false); + const error = new TypeError('oops'); const logEntry = logger .debug('example DEBUG log entry') .setMessage('some message') .setRecordId('some_record_Id') .setRecord({ Id: 'some_record_Id' }) - .setError(new TypeError('oops')) + .setError(error) .addTag('a tag') .addTags(['a second tag', 'a third tag']); expect(logger.getBufferSize()).toEqual(0); - expect(logger.getBufferSize()).toEqual(0); - expect(logEntry.loggingLevel).toEqual(undefined); - expect(logEntry.shouldSave).toEqual(false); - expect(logEntry.recordId).toEqual(undefined); - expect(logEntry.record).toEqual(undefined); - expect(logEntry.error).toEqual(undefined); - expect(logEntry.stack).toEqual(undefined); - expect(logEntry.timestamp).toEqual(undefined); - expect(logEntry.tags).toEqual(undefined); + expect(logEntry.loggingLevel).toEqual('DEBUG'); + expect(logEntry.recordId).toEqual('some_record_Id'); + expect(logEntry.record).toEqual({ Id: 'some_record_Id' }); + expect(logEntry.error).toBeTruthy(); + expect(logEntry.stack).toBeTruthy(); + expect(logEntry.error.message).toEqual(error.message); + expect(logEntry.error.stack).toEqual(error.stack); + expect(logEntry.timestamp).toBeTruthy(); + expect(logEntry.tags).toEqual(['a tag', 'a second tag', 'a third tag']); }); - it('still works for FINE logging level when disabled', async () => { + it('still works for FINE logging level when disabled when using deprecated markup approach', async () => { + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS, isEnabled: false }); const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); - logger.getUserSettings().isEnabled = false; + const settings = await logger.getUserSettings({ forceReload: true }); + expect(settings.isEnabled).toEqual(false); + const error = new TypeError('oops'); const logEntry = logger .fine('example FINE log entry') .setMessage('some message') .setRecordId('some_record_Id') .setRecord({ Id: 'some_record_Id' }) - .setError(new TypeError('oops')) + .setError(error) .addTag('a tag') .addTags(['a second tag', 'a third tag']); expect(logger.getBufferSize()).toEqual(0); - expect(logger.getBufferSize()).toEqual(0); - expect(logEntry.loggingLevel).toEqual(undefined); - expect(logEntry.shouldSave).toEqual(false); - expect(logEntry.recordId).toEqual(undefined); - expect(logEntry.record).toEqual(undefined); - expect(logEntry.error).toEqual(undefined); - expect(logEntry.stack).toEqual(undefined); - expect(logEntry.timestamp).toEqual(undefined); - expect(logEntry.tags).toEqual(undefined); + expect(logEntry.loggingLevel).toEqual('FINE'); + expect(logEntry.recordId).toEqual('some_record_Id'); + expect(logEntry.record).toEqual({ Id: 'some_record_Id' }); + expect(logEntry.error).toBeTruthy(); + expect(logEntry.stack).toBeTruthy(); + expect(logEntry.error.message).toEqual(error.message); + expect(logEntry.error.stack).toEqual(error.stack); + expect(logEntry.timestamp).toBeTruthy(); + expect(logEntry.tags).toEqual(['a tag', 'a second tag', 'a third tag']); }); - it('still works for FINER logging level when disabled', async () => { + it('still works for FINER logging level when disabled when using deprecated markup approach', async () => { + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS, isEnabled: false }); const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); - logger.getUserSettings().isEnabled = false; + const settings = await logger.getUserSettings({ forceReload: true }); + expect(settings.isEnabled).toEqual(false); + const error = new TypeError('oops'); const logEntry = logger .finer('example FINER log entry') .setMessage('some message') .setRecordId('some_record_Id') .setRecord({ Id: 'some_record_Id' }) - .setError(new TypeError('oops')) + .setError(error) .addTag('a tag') .addTags(['a second tag', 'a third tag']); expect(logger.getBufferSize()).toEqual(0); - expect(logger.getBufferSize()).toEqual(0); - expect(logEntry.loggingLevel).toEqual(undefined); - expect(logEntry.shouldSave).toEqual(false); - expect(logEntry.recordId).toEqual(undefined); - expect(logEntry.record).toEqual(undefined); - expect(logEntry.error).toEqual(undefined); - expect(logEntry.stack).toEqual(undefined); - expect(logEntry.timestamp).toEqual(undefined); - expect(logEntry.tags).toEqual(undefined); + expect(logEntry.loggingLevel).toEqual('FINER'); + expect(logEntry.recordId).toEqual('some_record_Id'); + expect(logEntry.record).toEqual({ Id: 'some_record_Id' }); + expect(logEntry.error).toBeTruthy(); + expect(logEntry.stack).toBeTruthy(); + expect(logEntry.error.message).toEqual(error.message); + expect(logEntry.error.stack).toEqual(error.stack); + expect(logEntry.timestamp).toBeTruthy(); + expect(logEntry.tags).toEqual(['a tag', 'a second tag', 'a third tag']); }); - it('still works for FINEST logging level when disabled', async () => { + it('still works for FINEST logging level when disabled when using deprecated markup approach', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); - logger.getUserSettings().isEnabled = false; + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS, isEnabled: true }); + const settings = await logger.getUserSettings(); + expect(settings.isEnabled).toEqual(false); + const error = new TypeError('oops'); const logEntry = logger .finest('example FINEST log entry') .setMessage('some message') .setRecordId('some_record_Id') .setRecord({ Id: 'some_record_Id' }) - .setError(new TypeError('oops')) + .setError(error) .addTag('a tag') .addTags(['a second tag', 'a third tag']); + await flushPromises(); expect(logger.getBufferSize()).toEqual(0); - expect(logger.getBufferSize()).toEqual(0); - expect(logEntry.loggingLevel).toEqual(undefined); - expect(logEntry.shouldSave).toEqual(false); - expect(logEntry.recordId).toEqual(undefined); - expect(logEntry.record).toEqual(undefined); - expect(logEntry.error).toEqual(undefined); - expect(logEntry.stack).toEqual(undefined); - expect(logEntry.timestamp).toEqual(undefined); - expect(logEntry.tags).toEqual(undefined); + expect(logEntry.loggingLevel).toEqual('FINEST'); + expect(logEntry.recordId).toEqual('some_record_Id'); + expect(logEntry.record).toEqual({ Id: 'some_record_Id' }); + expect(logEntry.error).toBeTruthy(); + expect(logEntry.stack).toBeTruthy(); + expect(logEntry.error.message).toEqual(error.message); + expect(logEntry.error.stack).toEqual(error.stack); + expect(logEntry.timestamp).toBeTruthy(); + expect(logEntry.tags).toEqual(['a tag', 'a second tag', 'a third tag']); }); - it('flushes buffer', async () => { + it('flushes buffer when using deprecated markup approach', async () => { + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); + const settings = await logger.getUserSettings({ forceReload: true }); + expect(settings.isEnabled).toEqual(true); const numberOfLogEntries = 3; for (let i = 0; i < numberOfLogEntries; i++) { logger.info('entry number: ' + i); } + await flushPromises(); expect(logger.getBufferSize()).toEqual(numberOfLogEntries); - logger.flushBuffer(); + await logger.flushBuffer(); + await flushPromises(); expect(logger.getBufferSize()).toEqual(0); }); - it('saves log entries and flushes buffer', async () => { + it('saves log entries and flushes buffer when using deprecated markup approach', async () => { + getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS }); const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - getSettings.emit({ ...MOCK_GET_SETTINGS }); + const settings = await logger.getUserSettings({ forceReload: true }); + expect(settings.isEnabled).toEqual(true); logger.info('example INFO log entry'); logger.debug('example DEBUG log entry'); + await flushPromises(); expect(logger.getBufferSize()).toBe(2); logger.saveLog(); - await Promise.resolve(); + await flushPromises(); expect(logger.getBufferSize()).toBe(0); }); }); 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 0b3b54472..321cb876e 100644 --- a/nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js +++ b/nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js @@ -4,22 +4,20 @@ //------------------------------------------------------------------------------------------------// const LogEntryBuilder = class { + #settingsPromise; + /** * @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} shouldSave Determines if the builder's instance of `LogEntryEvent__e` should be saved * @param {Boolean} isConsoleLoggingEnabled Determines if `console.log()` methods are execute */ - constructor(loggingLevel, shouldSave, isConsoleLoggingEnabled) { - this.shouldSave = shouldSave; - this.isConsoleLoggingEnabled = isConsoleLoggingEnabled; - if (this.shouldSave === true) { - this.loggingLevel = loggingLevel; - this.stack = new Error().stack; - this.timestamp = new Date().toISOString(); - this.tags = []; - } + constructor(loggingLevel, settingsPromise) { + this.#settingsPromise = settingsPromise; + this.loggingLevel = loggingLevel; + this.stack = new Error().stack; + this.timestamp = new Date().toISOString(); + this.tags = []; } /** @@ -28,10 +26,8 @@ const LogEntryBuilder = class { * @return {LogEntryBuilder} The same instance of `LogEntryBuilder`, useful for chaining methods */ setMessage(message) { - if (this.shouldSave === true) { - this.message = message; - this._logToConsole(); - } + this.message = message; + this._logToConsole(); return this; } @@ -41,9 +37,7 @@ const LogEntryBuilder = class { * @return {LogEntryBuilder} The same instance of `LogEntryBuilder`, useful for chaining methods */ setRecordId(recordId) { - if (this.shouldSave === true) { - this.recordId = recordId; - } + this.recordId = recordId; return this; } @@ -53,9 +47,7 @@ const LogEntryBuilder = class { * @return {LogEntryBuilder} The same instance of `LogEntryBuilder`, useful for chaining methods */ setRecord(record) { - if (this.shouldSave === true) { - this.record = record; - } + this.record = record; return this; } @@ -65,17 +57,15 @@ const LogEntryBuilder = class { * @return {LogEntryBuilder} The same instance of `LogEntryBuilder`, useful for chaining methods */ setError(error) { - if (this.shouldSave === true) { - this.error = {}; - if (error.body) { - this.error.message = error.body.message; - this.error.stack = error.body.stackTrace; - this.error.type = error.body.exceptionType; - } else { - this.error.message = error.message; - this.error.stack = error.stack; - this.error.type = 'JavaScript.' + error.name; - } + this.error = {}; + if (error.body) { + this.error.message = error.body.message; + this.error.stack = error.body.stackTrace; + this.error.type = error.body.exceptionType; + } else { + this.error.message = error.message; + this.error.stack = error.stack; + this.error.type = 'JavaScript.' + error.name; } return this; } @@ -86,11 +76,9 @@ const LogEntryBuilder = class { * @return {LogEntryBuilder} The same instance of `LogEntryBuilder`, useful for chaining methods */ addTag(tag) { - if (this.shouldSave === true) { - this.tags.push(tag); - // Deduplicate the list of tags - this.tags = Array.from(new Set(this.tags)); - } + this.tags.push(tag); + // Deduplicate the list of tags + this.tags = Array.from(new Set(this.tags)); return this; } @@ -107,28 +95,42 @@ const LogEntryBuilder = class { } _logToConsole() { - if (!this.isConsoleLoggingEnabled) { - return; - } + this.#settingsPromise().then(setting => { + this.isConsoleLoggingEnabled = setting.isConsoleLoggingEnabled; - /* eslint-disable no-console */ - switch (this.loggingLevel) { - case 'ERROR': - console.error(this.message, this); - break; - case 'WARN': - console.warn(this.message, this); - break; - case 'INFO': - console.info(this.message, this); - break; - default: - console.debug(this.message, this); - break; - } + if (!this.isConsoleLoggingEnabled) { + return; + } + + const consoleMessagePrefix = '%c Nebula Logger '; + const consoleFormatting = 'background: #0c598d; color: #fff; font-size: 12px; font-weight:bold;'; + let consoleLoggingFunction; + switch (this.loggingLevel) { + case 'ERROR': + consoleLoggingFunction = console.error; + break; + case 'WARN': + consoleLoggingFunction = console.warn; + break; + case 'INFO': + consoleLoggingFunction = console.info; + break; + default: + consoleLoggingFunction = console.debug; + break; + } + + consoleLoggingFunction(consoleMessagePrefix, consoleFormatting, this.message); + console.groupCollapsed(consoleMessagePrefix, consoleFormatting, 'Details for: ' + this.message); + // The use of JSON.parse(JSON.stringify()) is intended to help ensure that the output is readable, + // including handling proxy objects. If any cyclic objects are used, this approach could fail + consoleLoggingFunction(JSON.parse(JSON.stringify(this))); + console.trace(); + console.groupEnd(); + }); } }; -export function newLogEntry(loggingLevel, shouldSave, isConsoleLoggingEnabled) { - return new LogEntryBuilder(loggingLevel, shouldSave, isConsoleLoggingEnabled); +export function newLogEntry(loggingLevel, settingPromise) { + return new LogEntryBuilder(loggingLevel, settingPromise); } 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 369b072d8..1e40aca28 100644 --- a/nebula-logger/core/main/logger-engine/lwc/logger/logger.js +++ b/nebula-logger/core/main/logger-engine/lwc/logger/logger.js @@ -3,47 +3,33 @@ // See LICENSE file or go to https://github.com/jongpie/NebulaLogger for full license details. // //------------------------------------------------------------------------------------------------// -import { LightningElement, api, wire } from 'lwc'; -import { newLogEntry } from './logEntryBuilder'; -import getSettings from '@salesforce/apex/ComponentLogger.getSettings'; -import saveComponentLogEntries from '@salesforce/apex/ComponentLogger.saveComponentLogEntries'; +import { LightningElement, api } from 'lwc'; +import { createLoggerService } from './loggerService'; + +const CURRENT_VERSION_NUMBER = 'v4.10.2'; export default class Logger extends LightningElement { - componentLogEntries = []; - settings; - - _scenario; - - @wire(getSettings) - wiredSettings({ error, data }) { - if (data) { - this.settings = data; - } else if (error) { - /* eslint-disable-next-line no-console */ - console.error(error); - } - } + #loggerService = createLoggerService(); /** - * @description Returns information about the current user's settings, stored in `LoggerSettings__c` - * @return {ComponentLogger.ComponentLoggerSettings} The current user's instance of the Apex class `ComponentLogger.ComponentLoggerSettings` + * @description Returns **read-only** information about the current user's settings, stored in `LoggerSettings__c` + * @param {Object} parameters Object used to provide control over how user settings are retrieved. Currently, only the property `forceReload` is used. + * @return {Promise} The current user's instance of the Apex class `ComponentLogger.ComponentLoggerSettings` */ @api - getUserSettings() { - return this.settings; + getUserSettings(parameters = { forceReload: false }) { + return this.#loggerService.getUserSettings(parameters); } /** * @description Sets the scenario name for the current transaction - this is stored in `LogEntryEvent__e.Scenario__c` * and `Log__c.Scenario__c`, and can be used to filter & group logs * @param {String} scenario The name to use for the current transaction's scenario + * @return {Promise[]} A list of promises that be resolved before all scenarios are set */ @api setScenario(scenario) { - this._scenario = scenario; - this.componentLogEntries.forEach(logEntry => { - logEntry.scenario = this._scenario; - }); + return this.#loggerService.setScenario(scenario); } /** @@ -53,7 +39,7 @@ export default class Logger extends LightningElement { */ @api error(message) { - return this._newEntry('ERROR', message); + return this.#loggerService.error(message); } /** @@ -63,7 +49,7 @@ export default class Logger extends LightningElement { */ @api warn(message) { - return this._newEntry('WARN', message); + return this.#loggerService.warn(message); } /** @@ -73,7 +59,7 @@ export default class Logger extends LightningElement { */ @api info(message) { - return this._newEntry('INFO', message); + return this.#loggerService.info(message); } /** @@ -83,7 +69,7 @@ export default class Logger extends LightningElement { */ @api debug(message) { - return this._newEntry('DEBUG', message); + return this.#loggerService.debug(message); } /** @@ -93,7 +79,7 @@ export default class Logger extends LightningElement { */ @api fine(message) { - return this._newEntry('FINE', message); + return this.#loggerService.fine(message); } /** @@ -103,7 +89,7 @@ export default class Logger extends LightningElement { */ @api finer(message) { - return this._newEntry('FINER', message); + return this.#loggerService.finer(message); } /** @@ -113,7 +99,7 @@ export default class Logger extends LightningElement { */ @api finest(message) { - return this._newEntry('FINEST', message); + return this.#loggerService.finest(message); } /** @@ -122,58 +108,37 @@ export default class Logger extends LightningElement { */ @api getBufferSize() { - return this.componentLogEntries.length; + return this.#loggerService.getBufferSize(); } /** * @description Discards any entries that have been generated but not yet saved + * @return {Promise} A promise to clear the entries */ @api flushBuffer() { - this.componentLogEntries = []; + return this.#loggerService.flushBuffer(); } /** - * @description Saves any entries in Logger's buffer, using the specified save method for only this call. - * All subsequent calls to saveLog() will use the transaction save method. - * @param {String} saveMethod The enum value of Logger.SaveMethod to use for this specific save action. + * @description Saves any entries in Logger's buffer, using the specified save method for only this call + * All subsequent calls to saveLog() will use the transaction save method + * @param {String} saveMethod The enum value of Logger.SaveMethod to use for this specific save action */ @api saveLog(saveMethodName) { - if (this.getBufferSize() > 0) { - if (!saveMethodName && this.settings && this.settings.defaultSaveMethodName) { - saveMethodName = this.settings.defaultSaveMethodName; - } - - saveComponentLogEntries({ componentLogEntries: this.componentLogEntries, saveMethodName: saveMethodName }) - .then(this.flushBuffer()) - .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); - } - }); - } - } - - // Private functions - _meetsUserLoggingLevel(logEntryLoggingLevel) { - let logEntryLoggingLevelOrdinal = this.settings.supportedLoggingLevels[logEntryLoggingLevel]; - return this.settings && this.settings.isEnabled === true && this.settings.userLoggingLevel.ordinal <= logEntryLoggingLevelOrdinal; - } - - _newEntry(loggingLevel, message) { - const shouldSave = this._meetsUserLoggingLevel(loggingLevel); - const logEntryBuilder = newLogEntry(loggingLevel, shouldSave, this.settings.isConsoleLoggingEnabled).setMessage(message); - if (this._scenario) { - logEntryBuilder.scenario = this._scenario; - } - if (this._meetsUserLoggingLevel(loggingLevel) === true) { - this.componentLogEntries.push(logEntryBuilder); - } - - return logEntryBuilder; + this.#loggerService.saveLog(saveMethodName); } } + +/** + * @return {LoggerService} a LoggerService instance + */ +const createLogger = function () { + const consoleMessagePrefix = '%c Nebula Logger '; + const consoleFormatting = 'background: #0c598d; color: #fff;'; + console.info(consoleMessagePrefix, consoleFormatting, 'Nebula Logger Version Number: ' + CURRENT_VERSION_NUMBER); + return createLoggerService(); +}; + +export { createLogger }; diff --git a/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js b/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js new file mode 100644 index 000000000..616924664 --- /dev/null +++ b/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js @@ -0,0 +1,211 @@ +//------------------------------------------------------------------------------------------------// +// 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. // +//------------------------------------------------------------------------------------------------// + +import { newLogEntry } from './logEntryBuilder'; +import getSettings from '@salesforce/apex/ComponentLogger.getSettings'; +import saveComponentLogEntries from '@salesforce/apex/ComponentLogger.saveComponentLogEntries'; + +const LoggerService = class { + static settings = undefined; + + #componentLogEntries = []; + #scenario; + + /** + * @description Queue of promises to be logged. + */ + #loggingPromises = []; + + /** + * @description Returns **read-only** information about the current user's settings, stored in `LoggerSettings__c` + * @param {Object} parameters Object used to provide control over how user settings are retrieved. Currently, only the property `forceReload` is used. + * @return {Promise} The current user's instance of the Apex class `ComponentLogger.ComponentLoggerSettings` + */ + getUserSettings({ forceReload = false } = {}) { + return this._loadSettingsFromServer(forceReload).then(existingSettings => { + return Object.freeze({ + ...existingSettings, + supportedLoggingLevels: Object.freeze(existingSettings.supportedLoggingLevels), + userLoggingLevel: Object.freeze(existingSettings.userLoggingLevel) + }); + }); + } + + /** + * @description Sets the scenario name for the current transaction - this is stored in `LogEntryEvent__e.Scenario__c` + * and `Log__c.Scenario__c`, and can be used to filter & group logs + * @param {String} scenario The name to use for the current transaction's scenario + * @return {Promise[]} A list of promises that be resolved before all scenarios are set + */ + setScenario(scenario) { + this.#scenario = scenario; + return Promise.all(this.#loggingPromises).then( + this.#componentLogEntries.forEach(logEntry => { + logEntry.scenario = this.#scenario; + }) + ); + } + + /** + * @description Creates a new log entry with logging level == `LoggingLevel.ERROR` + * @param {String} message The string to use to set the entry's message field + * @return {LogEntryBuilder} The new entry's instance of `LogEntryEventBuilder`, useful for chaining methods + */ + error(message) { + return this._newEntry('ERROR', message); + } + + /** + * @description Creates a new log entry with logging level == `LoggingLevel.WARN` + * @param {String} message The string to use to set the entry's message field + * @return {LogEntryBuilder} The new entry's instance of `LogEntryEventBuilder`, useful for chaining methods + */ + warn(message) { + return this._newEntry('WARN', message); + } + + /** + * @description Creates a new log entry with logging level == `LoggingLevel.INFO` + * @param {String} message The string to use to set the entry's message field + * @return {LogEntryBuilder} The new entry's instance of `LogEntryEventBuilder`, useful for chaining methods + */ + info(message) { + return this._newEntry('INFO', message); + } + + /** + * @description Creates a new log entry with logging level == `LoggingLevel.DEBUG` + * @param {String} message The string to use to set the entry's message field + * @return {LogEntryBuilder} The new entry's instance of `LogEntryEventBuilder`, useful for chaining methods + */ + debug(message) { + return this._newEntry('DEBUG', message); + } + + /** + * @description Creates a new log entry with logging level == `LoggingLevel.FINE` + * @param {String} message The string to use to set the entry's message field + * @return {LogEntryBuilder} The new entry's instance of `LogEntryEventBuilder`, useful for chaining methods + */ + fine(message) { + return this._newEntry('FINE', message); + } + + /** + * @description Creates a new log entry with logging level == `LoggingLevel.FINER` + * @param {String} message The string to use to set the entry's message field + * @return {LogEntryBuilder} The new entry's instance of `LogEntryEventBuilder`, useful for chaining methods + */ + finer(message) { + return this._newEntry('FINER', message); + } + + /** + * @description Creates a new log entry with logging level == `LoggingLevel.FINEST` + * @param {String} message The string to use to set the entry's message field + * @return {LogEntryBuilder} The new entry's instance of `LogEntryEventBuilder`, useful for chaining methods + */ + finest(message) { + return this._newEntry('FINEST', message); + } + + /** + * @description Returns the number of entries that have been generated but not yet saved + * @return {Integer} The buffer's current size + */ + getBufferSize() { + return this.#componentLogEntries.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 = []; + }); + } + + /** + * @description Saves any entries in Logger's buffer, using the specified save method for only this call + * 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) { + let resolvedSaveMethodName; + if (!saveMethodName && LoggerService.settings && LoggerService.settings.defaultSaveMethodName) { + resolvedSaveMethodName = LoggerService.settings.defaultSaveMethodName; + } else { + resolvedSaveMethodName = saveMethodName; + } + + Promise.all(this.#loggingPromises) + .then(saveComponentLogEntries({ componentLogEntries: this.#componentLogEntries, saveMethodName: resolvedSaveMethodName })) + .then(this.flushBuffer()) + .catch(error => { + if (LoggerService.settings.isConsoleLoggingEnabled === true) { + /* eslint-disable-next-line no-console */ + console.error(error); + /* eslint-disable-next-line no-console */ + console.error(this.#componentLogEntries); + } + }); + } + } + + _loadSettingsFromServer(forceReload) { + // Loading only once + return LoggerService.settings === undefined || forceReload === true + ? getSettings() + .then(settings => { + LoggerService.settings = settings; + return settings; + }) + .catch(error => { + /* eslint-disable-next-line no-console */ + console.error(error); + }) + : new Promise(resolve => { + resolve(LoggerService.settings); + }); + } + + _meetsUserLoggingLevel(logEntryLoggingLevel) { + let logEntryLoggingLevelOrdinal = LoggerService.settings.supportedLoggingLevels[logEntryLoggingLevel]; + return ( + LoggerService.settings && + LoggerService.settings.isEnabled === true && + LoggerService.settings.userLoggingLevel.ordinal <= logEntryLoggingLevelOrdinal + ); + } + + _newEntry(loggingLevel, message) { + // Builder is returned immediately but console log will be determined after loadding 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); + } + }); + this.#loggingPromises.push(loggingPromise); + return logEntryBuilder; + } +}; + +/** + * @return {LoggerService} a LoggerService instance + */ +const createLoggerService = function () { + return new LoggerService(); +}; + +export { createLoggerService }; diff --git a/nebula-logger/recipes/applications/LoggerRecipes.app-meta.xml b/nebula-logger/recipes/applications/LoggerRecipes.app-meta.xml index 6e493457a..6b7fdbe56 100644 --- a/nebula-logger/recipes/applications/LoggerRecipes.app-meta.xml +++ b/nebula-logger/recipes/applications/LoggerRecipes.app-meta.xml @@ -13,6 +13,7 @@ Standard Logger_lwc_demo + Logger_lwc_legacy_demo LoggerAuraDemo Lightning Logger_Recipes_UtilityBar diff --git a/nebula-logger/recipes/classes/LoggerLWCDemoController.cls b/nebula-logger/recipes/classes/LoggerLWCDemoController.cls index 3099ad281..9cc7c0db0 100644 --- a/nebula-logger/recipes/classes/LoggerLWCDemoController.cls +++ b/nebula-logger/recipes/classes/LoggerLWCDemoController.cls @@ -1,5 +1,11 @@ @SuppressWarnings('PMD.ApexDoc') public with sharing class LoggerLWCDemoController { + @AuraEnabled(cacheable=true) + public static String returnSomeString() { + System.debug('hello, world!'); + return 'SomeString'; + } + @AuraEnabled public static void throwSomeError() { throw new System.DmlException('Some Error!!'); diff --git a/nebula-logger/recipes/lwc/loggerLWCDemo/loggerLWCDemo.html b/nebula-logger/recipes/lwc/loggerLWCDemo/loggerLWCDemo.html index 2928f9222..2d6d1aa70 100644 --- a/nebula-logger/recipes/lwc/loggerLWCDemo/loggerLWCDemo.html +++ b/nebula-logger/recipes/lwc/loggerLWCDemo/loggerLWCDemo.html @@ -6,9 +6,11 @@ -->