diff --git a/README.md b/README.md index 0ee1e2020..5747d126a 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ Designed for Salesforce admins, developers & architects. A robust logger for Apex, Lightning Components, Flow, Process Builder & Integrations. -## Unlocked Package - v4.7.5 +## Unlocked Package - v4.7.6 -[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015lkcQAA) -[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015lkcQAA) +[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015lkmQAA) +[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015lkmQAA) [![View Documentation](./images/btn-view-documentation.png)](https://jongpie.github.io/NebulaLogger/) ## Managed Package - v4.7.0 diff --git a/docs/lightning-components/LogEntryBuilder.md b/docs/lightning-components/LogEntryBuilder.md index 7871c275e..aeac35a00 100644 --- a/docs/lightning-components/LogEntryBuilder.md +++ b/docs/lightning-components/LogEntryBuilder.md @@ -74,9 +74,9 @@ Sets the log entry event's exception fields **Kind**: instance method of [LogEntryBuilder](#LogEntryBuilder) **Returns**: [LogEntryBuilder](#LogEntryBuilder) - The same instance of `LogEntryBuilder`, useful for chaining methods -| Param | Type | Description | -| ----- | ------------------ | --------------------------------------------------- | -| error | Error | The instance of a JavaScript `Error` object to use. | +| Param | Type | Description | +| ----- | ------------------ | -------------------------------------------------------------------------------- | +| error | Error | The instance of a JavaScript `Error` object to use, or an Apex HTTP error to use | diff --git a/nebula-logger/core/main/log-management/lwc/logViewer/__tests__/logViewer.test.js b/nebula-logger/core/main/log-management/lwc/logViewer/__tests__/logViewer.test.js index e639c3064..562540285 100644 --- a/nebula-logger/core/main/log-management/lwc/logViewer/__tests__/logViewer.test.js +++ b/nebula-logger/core/main/log-management/lwc/logViewer/__tests__/logViewer.test.js @@ -1,21 +1,17 @@ import { createElement } from 'lwc'; import LogViewer from 'c/logViewer'; import getLog from '@salesforce/apex/LogViewerController.getLog'; -import { registerApexTestWireAdapter } from '@salesforce/sfdx-lwc-jest'; -// Mock data -const mockGetLog = require('./data/LogViewerController.getLog.json'); - -// Register a test wire adapter -const getLogAdapter = registerApexTestWireAdapter(getLog); +const MOCK_GET_LOG = require('./data/LogViewerController.getLog.json'); document.execCommand = jest.fn(); jest.mock( '@salesforce/apex/LogViewerController.getLog', () => { + const { createApexTestWireAdapter } = require('@salesforce/sfdx-lwc-jest'); return { - default: () => mockGetLog + default: createApexTestWireAdapter(jest.fn()) }; }, { virtual: true } @@ -35,77 +31,67 @@ describe('Logger JSON Viewer lwc tests', () => { it('sets document title', async () => { const logViewerElement = createElement('c-log-viewer', { is: LogViewer }); document.body.appendChild(logViewerElement); - getLogAdapter.emit(mockGetLog); + getLog.emit({ ...MOCK_GET_LOG }); - await Promise.resolve(); - expect(logViewerElement.title).toEqual(mockGetLog.Name); + expect(logViewerElement.title).toEqual(MOCK_GET_LOG.Name); }); it('defaults to brand button variant', async () => { const logViewer = createElement('c-log-viewer', { is: LogViewer }); document.body.appendChild(logViewer); + getLog.emit({ ...MOCK_GET_LOG }); + await Promise.resolve('resolves component rerender after loading log record'); - getLogAdapter.emit(mockGetLog); - - await Promise.resolve(); const inputButton = logViewer.shadowRoot.querySelector('lightning-button-stateful'); - expect(logViewer.title).toEqual(mockGetLog.Name); + + expect(logViewer.title).toEqual(MOCK_GET_LOG.Name); expect(inputButton.variant).toEqual('brand'); }); it('copies the JSON to the clipboard', async () => { const logViewer = createElement('c-log-viewer', { is: LogViewer }); document.body.appendChild(logViewer); - - getLogAdapter.emit(mockGetLog); - - return Promise.resolve() - .then(() => { - let copyBtn = logViewer.shadowRoot.querySelector('lightning-button-stateful[data-id="copy-btn"]'); - copyBtn.click(); - }) - .then(() => { - const tab = logViewer.shadowRoot.querySelector('lightning-tab[data-id="json-content"]'); - expect(tab.value).toEqual('json'); - tab.dispatchEvent(new CustomEvent('active')); - }) - .then(() => { - const clipboardContent = JSON.parse(logViewer.shadowRoot.querySelector('pre').textContent); - expect(clipboardContent).toEqual(mockGetLog); - expect(document.execCommand).toHaveBeenCalledWith('copy'); - }); + getLog.emit({ ...MOCK_GET_LOG }); + await Promise.resolve('resolves component rerender after loading log record'); + + let copyBtn = logViewer.shadowRoot.querySelector('lightning-button-stateful[data-id="copy-btn"]'); + copyBtn.click(); + + await Promise.resolve('resolves copy-to-clipboard function'); + const tab = logViewer.shadowRoot.querySelector('lightning-tab[data-id="json-content"]'); + expect(tab.value).toEqual('json'); + tab.dispatchEvent(new CustomEvent('active')); + await Promise.resolve('resolves dispatchEvent() for tab'); + const clipboardContent = JSON.parse(logViewer.shadowRoot.querySelector('pre').textContent); + expect(clipboardContent).toEqual(MOCK_GET_LOG); + expect(document.execCommand).toHaveBeenCalledWith('copy'); }); it('copies the log file to the clipboard', async () => { const logViewer = createElement('c-log-viewer', { is: LogViewer }); document.body.appendChild(logViewer); - - getLogAdapter.emit(mockGetLog); - - return Promise.resolve() - .then(() => { - let copyBtn = logViewer.shadowRoot.querySelector('lightning-button-stateful[data-id="copy-btn"]'); - copyBtn.click(); - }) - .then(() => { - const tab = logViewer.shadowRoot.querySelector('lightning-tab[data-id="file-content"]'); - expect(tab.value).toEqual('file'); - tab.dispatchEvent(new CustomEvent('active')); - }) - .then(() => { - let expectedContentLines = []; - mockGetLog.LogEntries__r.forEach(logEntry => { - const columns = []; - columns.push('[' + new Date(logEntry.EpochTimestamp__c).toISOString() + ' - ' + logEntry.LoggingLevel__c + ']'); - columns.push('[Message]\n' + logEntry.Message__c); - columns.push('\n[Stack Trace]\n' + logEntry.StackTrace__c); - - expectedContentLines.push(columns.join('\n')); - }); - - const clipboardContent = logViewer.shadowRoot.querySelector('pre').textContent; - expect(clipboardContent).toEqual(expectedContentLines.join('\n\n' + '-'.repeat(36) + '\n\n')); - expect(document.execCommand).toHaveBeenCalledWith('copy'); - }); + getLog.emit({ ...MOCK_GET_LOG }); + await Promise.resolve('resolves component rerender after loading log record'); + + let copyBtn = logViewer.shadowRoot.querySelector('lightning-button-stateful[data-id="copy-btn"]'); + copyBtn.click(); + + await Promise.resolve('resolves copy-to-clipboard function'); + const tab = logViewer.shadowRoot.querySelector('lightning-tab[data-id="file-content"]'); + expect(tab.value).toEqual('file'); + tab.dispatchEvent(new CustomEvent('active')); + await Promise.resolve('resolves dispatchEvent() for tab'); + let expectedContentLines = []; + MOCK_GET_LOG.LogEntries__r.forEach(logEntry => { + const columns = []; + columns.push('[' + new Date(logEntry.EpochTimestamp__c).toISOString() + ' - ' + logEntry.LoggingLevel__c + ']'); + columns.push('[Message]\n' + logEntry.Message__c); + columns.push('\n[Stack Trace]\n' + logEntry.StackTrace__c); + + expectedContentLines.push(columns.join('\n')); + }); + const clipboardContent = logViewer.shadowRoot.querySelector('pre').textContent; + expect(clipboardContent).toEqual(expectedContentLines.join('\n\n' + '-'.repeat(36) + '\n\n')); + expect(document.execCommand).toHaveBeenCalledWith('copy'); }); }); diff --git a/nebula-logger/core/main/log-management/lwc/relatedLogEntries/__tests__/relatedLogEntries.test.js b/nebula-logger/core/main/log-management/lwc/relatedLogEntries/__tests__/relatedLogEntries.test.js index bdad494e7..684592f76 100644 --- a/nebula-logger/core/main/log-management/lwc/relatedLogEntries/__tests__/relatedLogEntries.test.js +++ b/nebula-logger/core/main/log-management/lwc/relatedLogEntries/__tests__/relatedLogEntries.test.js @@ -3,22 +3,15 @@ import { registerApexTestWireAdapter } from '@salesforce/sfdx-lwc-jest'; import RelatedLogEntries from 'c/relatedLogEntries'; import getQueryResult from '@salesforce/apex/RelatedLogEntriesController.getQueryResult'; -// Mock data -const mockRecordId = '0015400000gY3OuAAK'; -const mockQueryResult = require('./data/getQueryResult.json'); - -// Register a test wire adapter -const getQueryResultAdapter = registerApexTestWireAdapter(getQueryResult); - -function flushPromises() { - return new Promise(resolve => setTimeout(resolve, 0)); -} +const MOCK_RECORD_ID = '0015400000gY3OuAAK'; +const MOCK_QUERY_RESULT = require('./data/getQueryResult.json'); jest.mock( '@salesforce/apex/RelatedLogEntriesController.getQueryResult', () => { + const { createApexTestWireAdapter } = require('@salesforce/sfdx-lwc-jest'); return { - default: () => jest.fn() + default: createApexTestWireAdapter(jest.fn()) }; }, { virtual: true } @@ -34,16 +27,14 @@ describe('Related Log Entries lwc tests', () => { it('sets query result', async () => { const relatedLogEntriesElement = createElement('c-related-log-entries', { is: RelatedLogEntries }); - relatedLogEntriesElement.recordId = mockRecordId; + relatedLogEntriesElement.recordId = MOCK_RECORD_ID; document.body.appendChild(relatedLogEntriesElement); - getQueryResultAdapter.emit(mockQueryResult); + getQueryResult.emit({ ...MOCK_QUERY_RESULT }); - // Resolve a promise to wait for a rerender of the new content - return flushPromises().then(() => { - expect(relatedLogEntriesElement.queryResult).toBeTruthy(); - expect(relatedLogEntriesElement.queryResult.records[0].Id).toEqual(mockQueryResult.records[0].Id); - // expect(relatedLogEntriesElement.fieldSetName).not.toBe(undefined); - }); + await Promise.resolve(); + expect(relatedLogEntriesElement.queryResult).toBeTruthy(); + expect(relatedLogEntriesElement.queryResult.records[0].Id).toEqual(MOCK_QUERY_RESULT.records[0].Id); + // expect(relatedLogEntriesElement.fieldSetName).not.toBe(undefined); }); }); diff --git a/nebula-logger/core/main/logger-engine/classes/ComponentLogger.cls b/nebula-logger/core/main/logger-engine/classes/ComponentLogger.cls index 6e6623317..2746f607d 100644 --- a/nebula-logger/core/main/logger-engine/classes/ComponentLogger.cls +++ b/nebula-logger/core/main/logger-engine/classes/ComponentLogger.cls @@ -11,9 +11,6 @@ */ @SuppressWarnings('PMD.ExcessivePublicCount, PMD.StdCyclomaticComplexity') public inherited sharing class ComponentLogger { - @TestVisible - private static final String EXCEPTION_TYPE_PREFIX = 'JavaScript.'; - /** * @description Provides data to the frontend about `LoggerSettings__c` & server-supported logging details * @return return The instance of `ComponentLoggerSettings` for the current user @@ -37,10 +34,14 @@ public inherited sharing class ComponentLogger { for (ComponentLogEntry componentLogEntry : componentLogEntries) { Logger.setScenario(componentLogEntry.scenario); LoggingLevel entryLoggingLevel = Logger.getLoggingLevel(componentLogEntry.loggingLevel); - LogEntryEventBuilder logEntryEventBuilder = Logger.newEntry(entryLoggingLevel, componentLogEntry.message) - .setRecord(componentLogEntry.recordId) - .setRecord(componentLogEntry.record) - .addTags(componentLogEntry.tags); + LogEntryEventBuilder logEntryEventBuilder = Logger.newEntry(entryLoggingLevel, componentLogEntry.message).addTags(componentLogEntry.tags); + + if (componentLogEntry.recordId != null) { + logEntryEventBuilder.setRecord(componentLogEntry.recordId); + } + if (componentLogEntry.record != null) { + logEntryEventBuilder.setRecord(componentLogEntry.record); + } logEntryEventBuilder.getLogEntryEvent().Timestamp__c = componentLogEntry.timestamp; setComponentErrorDetails(logEntryEventBuilder, componentLogEntry.error); @@ -66,10 +67,10 @@ public inherited sharing class ComponentLogger { logEntryEventBuilder.getLogEntryEvent().ExceptionMessage__c = componentError.message; logEntryEventBuilder.getLogEntryEvent().ExceptionStackTrace__c = componentError.stack; - logEntryEventBuilder.getLogEntryEvent().ExceptionType__c = EXCEPTION_TYPE_PREFIX + componentError.type; + logEntryEventBuilder.getLogEntryEvent().ExceptionType__c = componentError.type; } - @SuppressWarnings('PMD.StdCyclomaticComplexity, PMD.CyclomaticComplexity, PMD.CognitiveComplexity, PMD.AvoidDeeplyNestedIfStmts') + @SuppressWarnings('PMD.AvoidDeeplyNestedIfStmts, PMD.CyclomaticComplexity, PMD.CognitiveComplexity, PMD.NcssMethodCount, PMD.StdCyclomaticComplexity') private static void setStackTraceDetails(LogEntryEventBuilder logEntryEventBuilder, String stackTraceString) { String originLocation; Boolean isAuraComponent = false; @@ -97,17 +98,23 @@ public inherited sharing class ComponentLogger { } stackTraceString = String.join(stackTraceLines, '\n'); - if (String.isNotBlank(stackTraceString)) { + if (String.isNotBlank(stackTraceString) == true) { String componentName; String componentFunction; - if (isAuraComponent == true) { - componentName = stackTraceLines.get(0).substringAfterLast(auraComponentContent).substringBefore('.js'); - componentFunction = stackTraceLines.get(0).substringBefore(' (').substringAfter('at '); - } else { - componentName = stackTraceLines.get(0).substringAfterLast(lwcModuleContent).substringBefore('.js'); - componentFunction = stackTraceLines.get(0).substringBefore(' (').substringAfter('at ').substringAfter('.'); + for (String currentStackTraceLine : stackTraceString.split('\n')) { + if (currentStackTraceLine.trim().startsWith('at eval') == true) { + continue; + } + + if (isAuraComponent == true) { + componentName = currentStackTraceLine.substringAfterLast(auraComponentContent).substringBefore('.js'); + componentFunction = currentStackTraceLine.substringBefore(' (').substringAfter('at '); + } else { + componentName = currentStackTraceLine.substringAfterLast(lwcModuleContent).substringBefore('.js'); + componentFunction = currentStackTraceLine.substringBefore(' (').substringAfter('at ').substringAfter('.'); + } + break; } - originLocation = componentName + '.' + componentFunction; } } diff --git a/nebula-logger/core/main/logger-engine/classes/Logger.cls b/nebula-logger/core/main/logger-engine/classes/Logger.cls index a3a8126a3..b7f368a05 100644 --- a/nebula-logger/core/main/logger-engine/classes/Logger.cls +++ b/nebula-logger/core/main/logger-engine/classes/Logger.cls @@ -13,7 +13,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.7.5'; + private static final String CURRENT_VERSION_NUMBER = 'v4.7.6'; private static final LoggingLevel DEFAULT_LOGGING_LEVEL = LoggingLevel.DEBUG; private static final List LOG_ENTRIES_BUFFER = new List(); private static final Map MOCK_SCENARIO_TO_SCENARIO_RULE = new Map(); 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 a8c6a967e..187a3875e 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,16 +1,15 @@ import { createElement } from 'lwc'; import Logger from 'c/logger'; import getSettings from '@salesforce/apex/ComponentLogger.getSettings'; -import { registerApexTestWireAdapter } from '@salesforce/sfdx-lwc-jest'; const MOCK_GET_SETTINGS = require('./data/getLoggerSettings.json'); -const getSettingsAdapter = registerApexTestWireAdapter(getSettings); jest.mock( '@salesforce/apex/ComponentLogger.getSettings', () => { + const { createApexTestWireAdapter } = require('@salesforce/sfdx-lwc-jest'); return { - default: () => MOCK_GET_SETTINGS + default: createApexTestWireAdapter(jest.fn()) }; }, { virtual: true } @@ -22,464 +21,443 @@ describe('Logger lwc tests', () => { document.body.removeChild(document.body.firstChild); } jest.clearAllMocks(); - // jest.restoreAllMocks(); }); + it('returns user settings', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); + getSettings.emit({ ...MOCK_GET_SETTINGS }); - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); - - // await Promise.resolve(); const userSettings = 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 () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); + getSettings.emit({ ...MOCK_GET_SETTINGS }); logger.getUserSettings().isEnabled = true; - - return Promise.resolve().then(() => { - const scenario = 'some scenario'; - const message = 'some message'; - const firstLogEntry = logger.finest(message); - expect(firstLogEntry.scenario).toBeUndefined(); - expect(logger.getBufferSize()).toEqual(1); - - const secondLogEntry = logger.info(message); - expect(secondLogEntry.scenario).toBeUndefined(); - expect(logger.getBufferSize()).toEqual(2); - - logger.setScenario(scenario); - - expect(firstLogEntry.scenario).toEqual(scenario); - expect(secondLogEntry.scenario).toEqual(scenario); - }); + const scenario = 'some scenario'; + const message = 'some message'; + const firstLogEntry = logger.finest(message); + expect(firstLogEntry.scenario).toBeUndefined(); + expect(logger.getBufferSize()).toEqual(1); + const secondLogEntry = logger.info(message); + expect(secondLogEntry.scenario).toBeUndefined(); + expect(logger.getBufferSize()).toEqual(2); + + logger.setScenario(scenario); + + expect(firstLogEntry.scenario).toEqual(scenario); + expect(secondLogEntry.scenario).toEqual(scenario); }); + it('logs an ERROR entry', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); + getSettings.emit({ ...MOCK_GET_SETTINGS }); - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); + const message = 'component log entry with loggingLevel ERROR'; + const logEntry = logger.error(message); - return Promise.resolve().then(() => { - const message = 'component log entry with loggingLevel ERROR'; - const logEntry = logger.error(message); - - expect(logger.getBufferSize()).toEqual(1); - expect(logEntry.loggingLevel).toEqual('ERROR'); - expect(logEntry.message).toEqual(message); - }); + expect(logger.getBufferSize()).toEqual(1); + expect(logEntry.loggingLevel).toEqual('ERROR'); + expect(logEntry.message).toEqual(message); }); + it('logs a WARN entry', async () => { const logger = createElement('c-logger', { is: Logger }); - document.body.appendChild(logger); - - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); + getSettings.emit({ ...MOCK_GET_SETTINGS }); - return Promise.resolve().then(() => { - const message = 'component log entry with loggingLevel WARN'; - const logEntry = logger.warn(message); + const message = 'component log entry with loggingLevel WARN'; + const logEntry = logger.warn(message); - // const logEntries = logger.getBuffer(); - expect(logger.getBufferSize()).toEqual(1); - expect(logEntry.loggingLevel).toEqual('WARN'); - expect(logEntry.message).toEqual(message); - }); + expect(logger.getBufferSize()).toEqual(1); + expect(logEntry.loggingLevel).toEqual('WARN'); + expect(logEntry.message).toEqual(message); }); + it('logs an INFO entry', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); + getSettings.emit({ ...MOCK_GET_SETTINGS }); - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); - - return Promise.resolve().then(() => { - const message = 'component log entry with loggingLevel INFO'; - const logEntry = logger.info(message); + const message = 'component log entry with loggingLevel INFO'; + const logEntry = logger.info(message); - expect(logger.getBufferSize()).toEqual(1); - expect(logEntry.loggingLevel).toEqual('INFO'); - expect(logEntry.message).toEqual(message); - }); + expect(logger.getBufferSize()).toEqual(1); + expect(logEntry.loggingLevel).toEqual('INFO'); + expect(logEntry.message).toEqual(message); }); + it('logs a DEBUG entry', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); + getSettings.emit({ ...MOCK_GET_SETTINGS }); - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); - - return Promise.resolve().then(() => { - const message = 'component log entry with loggingLevel DEBUG'; - const logEntry = logger.debug(message); + const message = 'component log entry with loggingLevel DEBUG'; + const logEntry = logger.debug(message); - expect(logger.getBufferSize()).toEqual(1); - expect(logEntry.loggingLevel).toEqual('DEBUG'); - expect(logEntry.message).toEqual(message); - }); + expect(logger.getBufferSize()).toEqual(1); + expect(logEntry.loggingLevel).toEqual('DEBUG'); + expect(logEntry.message).toEqual(message); }); + it('logs a FINE entry', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); + getSettings.emit({ ...MOCK_GET_SETTINGS }); - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); - - return Promise.resolve().then(() => { - const message = 'component log entry with loggingLevel FINE'; - const logEntry = logger.fine(message); + const message = 'component log entry with loggingLevel FINE'; + const logEntry = logger.fine(message); - expect(logger.getBufferSize()).toEqual(1); - expect(logEntry.loggingLevel).toEqual('FINE'); - expect(logEntry.message).toEqual(message); - }); + expect(logger.getBufferSize()).toEqual(1); + expect(logEntry.loggingLevel).toEqual('FINE'); + expect(logEntry.message).toEqual(message); }); + it('logs a FINER entry', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); + getSettings.emit({ ...MOCK_GET_SETTINGS }); - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); - - return Promise.resolve().then(() => { - const message = 'component log entry with loggingLevel FINER'; - const logEntry = logger.finer(message); + const message = 'component log entry with loggingLevel FINER'; + const logEntry = logger.finer(message); - expect(logger.getBufferSize()).toEqual(1); - expect(logEntry.loggingLevel).toEqual('FINER'); - expect(logEntry.message).toEqual(message); - }); + expect(logger.getBufferSize()).toEqual(1); + expect(logEntry.loggingLevel).toEqual('FINER'); + expect(logEntry.message).toEqual(message); }); + it('logs a FINEST entry', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); + getSettings.emit({ ...MOCK_GET_SETTINGS }); - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); - - return Promise.resolve().then(() => { - const message = 'component log entry with loggingLevel FINEST'; - const logEntry = logger.finest(message); + const message = 'component log entry with loggingLevel FINEST'; + const logEntry = logger.finest(message); - expect(logger.getBufferSize()).toEqual(1); - expect(logEntry.loggingLevel).toEqual('FINEST'); - expect(logEntry.message).toEqual(message); - }); + expect(logger.getBufferSize()).toEqual(1); + expect(logEntry.loggingLevel).toEqual('FINEST'); + expect(logEntry.message).toEqual(message); }); + it('sets recordId', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); + getSettings.emit({ ...MOCK_GET_SETTINGS }); + const logEntry = logger.info('example log entry'); + expect(logEntry.recordId).toBeFalsy(); - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); - - return Promise.resolve().then(() => { - const logEntry = logger.info('example log entry'); - expect(logEntry.recordId).toBeFalsy(); + const mockUserId = '0052F000008yLcEQAU'; + logEntry.setRecordId(mockUserId); - const mockUserId = '0052F000008yLcEQAU'; - logEntry.setRecordId(mockUserId); - expect(logEntry.recordId).toEqual(mockUserId); - }); + expect(logEntry.recordId).toEqual(mockUserId); }); + it('sets record', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); + getSettings.emit({ ...MOCK_GET_SETTINGS }); + const logEntry = logger.info('example log entry'); + expect(logEntry.record).toBeFalsy(); + const mockUserRecord = { Id: '0052F000008yLcEQAU', FirstName: 'Jonathan', LastName: 'Gillespie' }; - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); - - return Promise.resolve().then(() => { - const logEntry = logger.info('example log entry'); - expect(logEntry.record).toBeFalsy(); + logEntry.setRecord(mockUserRecord); - const mockUserId = '0052F000008yLcEQAU'; - const mockUserRecord = { Id: mockUserId, FirstName: 'Jonathan', LastName: 'Gillespie' }; - logEntry.setRecord(mockUserRecord); - expect(logEntry.record).toEqual(mockUserRecord); - }); + expect(logEntry.record).toEqual(mockUserRecord); }); - it('sets error', async () => { + + it('sets JavaScript error details', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); + getSettings.emit({ ...MOCK_GET_SETTINGS }); + 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'); + }); - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); - - return Promise.resolve().then(() => { - const logEntry = logger.info('example log entry'); - expect(logEntry.error).toBeFalsy(); + it('sets Apex error details', async () => { + const logger = createElement('c-logger', { is: Logger }); + document.body.appendChild(logger); + getSettings.emit({ ...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(); - let error = new TypeError('oops'); - expect(error).toBeTruthy(); - expect(error.message).toBeTruthy(); - expect(error.stack).toBeTruthy(); + logEntry.setError(error); - logEntry.setError(error); - expect(logEntry.error.message).toEqual(error.message); - expect(logEntry.error.stack).toEqual(error.stack); - expect(logEntry.error.type).toEqual('TypeError'); - }); + 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', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); + getSettings.emit({ ...MOCK_GET_SETTINGS }); + const logEntry = logger.info('example log entry'); + expect(logEntry.recordId).toBeFalsy(); + const mockTags = ['first tag', 'second tag', 'third tag']; - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); - - return Promise.resolve().then(() => { - const logEntry = logger.info('example log entry'); - expect(logEntry.recordId).toBeFalsy(); + logEntry.addTags(mockTags); - const mockTags = ['first tag', 'second tag', 'third tag']; - logEntry.addTags(mockTags); - expect(logEntry.tags.length).toEqual(mockTags.length); - }); + expect(logEntry.tags.length).toEqual(mockTags.length); }); + it('deduplicates tags', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); + getSettings.emit({ ...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]); + } - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); - - return Promise.resolve().then(() => { - 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); - }); + expect(logEntry.tags.length).toEqual(1); }); - it('still works for ERROR when disabled', async () => { + + it('still works for ERROR logging level when disabled', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); + getSettings.emit({ ...MOCK_GET_SETTINGS }); logger.getUserSettings().isEnabled = false; - return Promise.resolve().then(() => { - const logEntry = logger - .error('example ERROR log entry') - .setMessage('some message') - .setRecordId('some_record_Id') - .setRecord({ Id: 'some_record_Id' }) - .setError(new TypeError('oops')) - .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); - }); + const logEntry = logger + .error('example ERROR log entry') + .setMessage('some message') + .setRecordId('some_record_Id') + .setRecord({ Id: 'some_record_Id' }) + .setError(new TypeError('oops')) + .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); }); - it('still works for WARN when disabled', async () => { + + it('still works for WARN logging level when disabled', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); + getSettings.emit({ ...MOCK_GET_SETTINGS }); logger.getUserSettings().isEnabled = false; - return Promise.resolve().then(() => { - const logEntry = logger - .warn('example WARN log entry') - .setMessage('some message') - .setRecordId('some_record_Id') - .setRecord({ Id: 'some_record_Id' }) - .setError(new TypeError('oops')) - .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); - }); + const logEntry = logger + .warn('example WARN log entry') + .setMessage('some message') + .setRecordId('some_record_Id') + .setRecord({ Id: 'some_record_Id' }) + .setError(new TypeError('oops')) + .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); }); - it('still works for INFO when disabled', async () => { + + it('still works for INFO logging level when disabled', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); + getSettings.emit({ ...MOCK_GET_SETTINGS }); logger.getUserSettings().isEnabled = false; - return Promise.resolve().then(() => { - const logEntry = logger - .info('example INFO log entry') - .setMessage('some message') - .setRecordId('some_record_Id') - .setRecord({ Id: 'some_record_Id' }) - .setError(new TypeError('oops')) - .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); - }); + const logEntry = logger + .info('example INFO log entry') + .setMessage('some message') + .setRecordId('some_record_Id') + .setRecord({ Id: 'some_record_Id' }) + .setError(new TypeError('oops')) + .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); }); - it('still works for DEBUG when disabled', async () => { + + it('still works for DEBUG logging level when disabled', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); + getSettings.emit({ ...MOCK_GET_SETTINGS }); logger.getUserSettings().isEnabled = false; - return Promise.resolve().then(() => { - const logEntry = logger - .debug('example DEBUG log entry') - .setMessage('some message') - .setRecordId('some_record_Id') - .setRecord({ Id: 'some_record_Id' }) - .setError(new TypeError('oops')) - .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); - }); + const logEntry = logger + .debug('example DEBUG log entry') + .setMessage('some message') + .setRecordId('some_record_Id') + .setRecord({ Id: 'some_record_Id' }) + .setError(new TypeError('oops')) + .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); }); - it('still works for FINE when disabled', async () => { + + it('still works for FINE logging level when disabled', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); + getSettings.emit({ ...MOCK_GET_SETTINGS }); logger.getUserSettings().isEnabled = false; - return Promise.resolve().then(() => { - const logEntry = logger - .fine('example FINE log entry') - .setMessage('some message') - .setRecordId('some_record_Id') - .setRecord({ Id: 'some_record_Id' }) - .setError(new TypeError('oops')) - .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); - }); + const logEntry = logger + .fine('example FINE log entry') + .setMessage('some message') + .setRecordId('some_record_Id') + .setRecord({ Id: 'some_record_Id' }) + .setError(new TypeError('oops')) + .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); }); - it('still works for FINER when disabled', async () => { + + it('still works for FINER logging level when disabled', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); + getSettings.emit({ ...MOCK_GET_SETTINGS }); logger.getUserSettings().isEnabled = false; - return Promise.resolve().then(() => { - const logEntry = logger - .finer('example FINER log entry') - .setMessage('some message') - .setRecordId('some_record_Id') - .setRecord({ Id: 'some_record_Id' }) - .setError(new TypeError('oops')) - .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); - }); + const logEntry = logger + .finer('example FINER log entry') + .setMessage('some message') + .setRecordId('some_record_Id') + .setRecord({ Id: 'some_record_Id' }) + .setError(new TypeError('oops')) + .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); }); - it('still works for FINEST when disabled', async () => { + + it('still works for FINEST logging level when disabled', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); - - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); + getSettings.emit({ ...MOCK_GET_SETTINGS }); logger.getUserSettings().isEnabled = false; - return Promise.resolve().then(() => { - const logEntry = logger - .finest('example FINEST log entry') - .setMessage('some message') - .setRecordId('some_record_Id') - .setRecord({ Id: 'some_record_Id' }) - .setError(new TypeError('oops')) - .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); - }); + const logEntry = logger + .finest('example FINEST log entry') + .setMessage('some message') + .setRecordId('some_record_Id') + .setRecord({ Id: 'some_record_Id' }) + .setError(new TypeError('oops')) + .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); }); + it('flushes buffer', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); + getSettings.emit({ ...MOCK_GET_SETTINGS }); + const numberOfLogEntries = 3; + for (let i = 0; i < numberOfLogEntries; i++) { + logger.info('entry number: ' + i); + } + expect(logger.getBufferSize()).toEqual(numberOfLogEntries); - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); + logger.flushBuffer(); - return Promise.resolve().then(() => { - const numberOfLogEntries = 3; - for (let i = 0; i < numberOfLogEntries; i++) { - logger.info('entry number: ' + i); - } - - expect(logger.getBufferSize()).toEqual(numberOfLogEntries); - logger.flushBuffer(); - expect(logger.getBufferSize()).toEqual(0); - }); + expect(logger.getBufferSize()).toEqual(0); }); + it('saves log entries and flushes buffer', async () => { const logger = createElement('c-logger', { is: Logger }); document.body.appendChild(logger); + getSettings.emit({ ...MOCK_GET_SETTINGS }); + logger.info('example INFO log entry'); + logger.debug('example DEBUG log entry'); + expect(logger.getBufferSize()).toBe(2); - getSettingsAdapter.emit({ ...MOCK_GET_SETTINGS }); - - return Promise.resolve() - .then(() => { - logger.info('example INFO log entry'); - logger.debug('example DEBUG log entry'); - expect(logger.getBufferSize()).toBe(2); + logger.saveLog(); - logger.saveLog(); - }) - .then(() => { - expect(logger.getBufferSize()).toBe(0); - }); + await Promise.resolve(); + 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 8c0bcd28c..0b3b54472 100644 --- a/nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js +++ b/nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js @@ -61,15 +61,21 @@ const LogEntryBuilder = class { /** * @description Sets the log entry event's exception fields - * @param {Error} error The instance of a JavaScript `Error` object to use. + * @param {Error} error The instance of a JavaScript `Error` object to use, or an Apex HTTP error to use * @return {LogEntryBuilder} The same instance of `LogEntryBuilder`, useful for chaining methods */ setError(error) { if (this.shouldSave === true) { this.error = {}; - this.error.message = error.message; - this.error.stack = error.stack; - this.error.type = error.name; + 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; } diff --git a/nebula-logger/core/tests/logger-engine/classes/ComponentLogger_Tests.cls b/nebula-logger/core/tests/logger-engine/classes/ComponentLogger_Tests.cls index 3a9daf24e..f5faa661a 100644 --- a/nebula-logger/core/tests/logger-engine/classes/ComponentLogger_Tests.cls +++ b/nebula-logger/core/tests/logger-engine/classes/ComponentLogger_Tests.cls @@ -117,9 +117,9 @@ private class ComponentLogger_Tests { LoggerDataStore.setMock(LoggerMockDataStore.getEventBus()); User currentUser = new User(FirstName = UserInfo.getFirstName(), Id = UserInfo.getUserId(), ProfileId = UserInfo.getProfileId()); ComponentLogger.ComponentError mockComponentError = new ComponentLogger.ComponentError(); - mockComponentError.message = 'some javascript error message'; + mockComponentError.message = 'some JavaScript error message'; mockComponentError.stack = 'some \nstack \ntrace \nstring'; - mockComponentError.type = 'ReferenceError'; + mockComponentError.type = 'JavaScript.ReferenceError'; ComponentLogger.ComponentLogEntry componentLogEntry = new ComponentLogger.ComponentLogEntry(); componentLogEntry.error = mockComponentError; componentLogEntry.loggingLevel = LoggingLevel.INFO.name(); @@ -144,7 +144,43 @@ private class ComponentLogger_Tests { System.assertEquals(componentLogEntry.timestamp, publishedLogEntryEvent.Timestamp__c); System.assertEquals(componentLogEntry.error.message, publishedLogEntryEvent.ExceptionMessage__c); System.assertEquals(componentLogEntry.error.stack, publishedLogEntryEvent.ExceptionStackTrace__c); - System.assertEquals(ComponentLogger.EXCEPTION_TYPE_PREFIX + componentLogEntry.error.type, publishedLogEntryEvent.ExceptionType__c); + System.assertEquals(componentLogEntry.error.type, publishedLogEntryEvent.ExceptionType__c); + } + + @IsTest + static void it_should_save_component_log_entry_with_apex_controller_error() { + LoggerDataStore.setMock(LoggerMockDataStore.getEventBus()); + User currentUser = new User(FirstName = UserInfo.getFirstName(), Id = UserInfo.getUserId(), ProfileId = UserInfo.getProfileId()); + Exception mockApexException = new DmlException('It is with much sadness that I must inform you that this DML statement was most unsuccessful'); + ComponentLogger.ComponentError mockComponentError = new ComponentLogger.ComponentError(); + mockComponentError.message = mockApexException.getMessage(); + mockComponentError.stack = mockApexException.getStackTraceString(); + mockComponentError.type = mockApexException.getTypeName(); + ComponentLogger.ComponentLogEntry componentLogEntry = new ComponentLogger.ComponentLogEntry(); + componentLogEntry.error = mockComponentError; + componentLogEntry.loggingLevel = LoggingLevel.INFO.name(); + componentLogEntry.message = 'hello, world'; + componentLogEntry.recordId = currentUser.Id; + componentLogEntry.record = currentUser; + componentLogEntry.timestamp = System.now().addDays(-1 / 24); + componentLogEntry.tags = new List{ 'some tag', 'one more tag' }; + System.assertEquals(0, Logger.saveLogCallCount); + System.assertEquals(0, LoggerMockDataStore.getEventBus().getPublishCallCount()); + System.assertEquals(0, LoggerMockDataStore.getEventBus().getPublishedPlatformEvents().size()); + + ComponentLogger.saveComponentLogEntries(new List{ componentLogEntry }, null); + + System.assertEquals(1, Logger.saveLogCallCount); + System.assertEquals(1, LoggerMockDataStore.getEventBus().getPublishCallCount()); + System.assertEquals(1, LoggerMockDataStore.getEventBus().getPublishedPlatformEvents().size()); + LogEntryEvent__e publishedLogEntryEvent = (LogEntryEvent__e) LoggerMockDataStore.getEventBus().getPublishedPlatformEvents().get(0); + System.assertEquals('Component', publishedLogEntryEvent.OriginType__c); + System.assertEquals(componentLogEntry.loggingLevel, publishedLogEntryEvent.LoggingLevel__c); + System.assertEquals(componentLogEntry.message, publishedLogEntryEvent.Message__c); + System.assertEquals(componentLogEntry.timestamp, publishedLogEntryEvent.Timestamp__c); + System.assertEquals(componentLogEntry.error.message, publishedLogEntryEvent.ExceptionMessage__c); + System.assertEquals(componentLogEntry.error.stack, publishedLogEntryEvent.ExceptionStackTrace__c); + System.assertEquals(componentLogEntry.error.type, publishedLogEntryEvent.ExceptionType__c); } @IsTest diff --git a/nebula-logger/recipes/classes/LoggerLWCDemoController.cls b/nebula-logger/recipes/classes/LoggerLWCDemoController.cls new file mode 100644 index 000000000..1f2429f64 --- /dev/null +++ b/nebula-logger/recipes/classes/LoggerLWCDemoController.cls @@ -0,0 +1,7 @@ +@SuppressWarnings('PMD.ApexDoc') +public with sharing class LoggerLWCDemoController { + @AuraEnabled + public static void throwSomeError() { + throw new DmlException('Some Error!!'); + } +} diff --git a/nebula-logger/recipes/classes/LoggerLWCDemoController.cls-meta.xml b/nebula-logger/recipes/classes/LoggerLWCDemoController.cls-meta.xml new file mode 100644 index 000000000..891916bb0 --- /dev/null +++ b/nebula-logger/recipes/classes/LoggerLWCDemoController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 54.0 + Active + diff --git a/nebula-logger/recipes/lwc/loggerLWCDemo/loggerLWCDemo.html b/nebula-logger/recipes/lwc/loggerLWCDemo/loggerLWCDemo.html index 646490b0a..2928f9222 100644 --- a/nebula-logger/recipes/lwc/loggerLWCDemo/loggerLWCDemo.html +++ b/nebula-logger/recipes/lwc/loggerLWCDemo/loggerLWCDemo.html @@ -14,6 +14,7 @@
+ diff --git a/nebula-logger/recipes/lwc/loggerLWCDemo/loggerLWCDemo.js b/nebula-logger/recipes/lwc/loggerLWCDemo/loggerLWCDemo.js index 0ebd8f740..37a711ebe 100644 --- a/nebula-logger/recipes/lwc/loggerLWCDemo/loggerLWCDemo.js +++ b/nebula-logger/recipes/lwc/loggerLWCDemo/loggerLWCDemo.js @@ -5,6 +5,7 @@ /* eslint-disable no-console */ import { LightningElement } from 'lwc'; +import throwSomeError from '@salesforce/apex/LoggerLWCDemoController.throwSomeError'; const LOGGER_NAME = 'c-logger'; @@ -25,6 +26,22 @@ export default class LoggerLWCDemo extends LightningElement { this.tagsString = event.target.value; } + async logApexErrorExample() { + console.log('running logApexError for btn'); + const logger = this.template.querySelector(LOGGER_NAME); + console.log(JSON.parse(JSON.stringify(logger))); + await throwSomeError() + .then(result => { + console.log('the result', JSON.parse(JSON.stringify(result))); + }) + .catch(error => { + console.log('apex error', error); + console.log('and a stack trace', new Error().stack); + const entry = logger.error(this.message).setError(error).addTag('lwc logging demo'); + console.log('entry==', JSON.parse(JSON.stringify(entry))); + }); + } + logErrorExample() { console.log('running logError for btn'); const logger = this.template.querySelector(LOGGER_NAME); diff --git a/nebula-logger/recipes/profiles/Admin.profile-meta.xml b/nebula-logger/recipes/profiles/Admin.profile-meta.xml index 949410f39..cf6ddd84c 100644 --- a/nebula-logger/recipes/profiles/Admin.profile-meta.xml +++ b/nebula-logger/recipes/profiles/Admin.profile-meta.xml @@ -2557,6 +2557,10 @@ Log__c Hidden + + Logger_lwc_demo + DefaultOn + LoggerSettings Hidden @@ -2566,700 +2570,4 @@ Hidden Salesforce - - true - ActivateContract - - - true - ActivateOrder - - - true - ActivitiesAccess - - - true - AddDirectMessageMembers - - - true - AllowUniversalSearch - - - true - AllowViewKnowledge - - - true - ApexRestServices - - - true - ApiEnabled - - - true - ApproveContract - - - true - AssignPermissionSets - - - true - AssignTopics - - - true - AuthorApex - - - true - BulkMacrosAllowed - - - true - CanInsertFeedSystemFields - - - true - CanUseNewDashboardBuilder - - - true - CanVerifyComment - - - true - ChangeDashboardColors - - - true - ChatterEditOwnPost - - - true - ChatterEditOwnRecordPost - - - true - ChatterFileLink - - - true - ChatterInternalUser - - - true - ChatterInviteExternalUsers - - - true - ChatterOwnGroups - - - true - ConnectOrgToEnvironmentHub - - - true - ConsentApiUpdate - - - true - ContentAdministrator - - - true - ContentWorkspaces - - - true - ConvertLeads - - - true - CreateCustomizeDashboards - - - true - CreateCustomizeFilters - - - true - CreateCustomizeReports - - - true - CreateDashboardFolders - - - true - CreateLtngTempFolder - - - true - CreateReportFolders - - - true - CreateTopics - - - true - CreateWorkspaces - - - true - CustomizeApplication - - - true - DataExport - - - true - DelegatedTwoFactor - - - true - DeleteActivatedContract - - - true - DeleteTopics - - - true - DistributeFromPersWksp - - - true - EditActivatedOrders - - - true - EditBrandTemplates - - - true - EditCaseComments - - - true - EditEvent - - - true - EditHtmlTemplates - - - true - EditKnowledge - - - true - EditMyDashboards - - - true - EditMyReports - - - true - EditOppLineItemUnitPrice - - - true - EditPublicDocuments - - - true - EditPublicFilters - - - true - EditPublicTemplates - - - true - EditReadonlyFields - - - true - EditTask - - - true - EditTopics - - - true - EmailMass - - - true - EmailSingle - - - true - EnableCommunityAppLauncher - - - true - EnableNotifications - - - true - ExportReport - - - true - GiveRecognitionBadge - - - true - ImportCustomObjects - - - true - ImportLeads - - - true - ImportPersonal - - - true - InboundMigrationToolsUser - - - true - InstallPackaging - - - true - LightningConsoleAllowedForUser - - - true - LightningExperienceUser - - - true - ListEmailSend - - - true - ManageAnalyticSnapshots - - - true - ManageAuthProviders - - - true - ManageBusinessHourHolidays - - - true - ManageC360AConnections - - - true - ManageCMS - - - true - ManageCallCenters - - - true - ManageCases - - - true - ManageCategories - - - true - ManageCertificates - - - true - ManageContentPermissions - - - true - ManageContentProperties - - - true - ManageContentTypes - - - true - ManageCustomPermissions - - - true - ManageCustomReportTypes - - - true - ManageDashbdsInPubFolders - - - true - ManageDataCategories - - - true - ManageDataIntegrations - - - true - ManageDynamicDashboards - - - true - ManageEmailClientConfig - - - true - ManageEntitlements - - - true - ManageExchangeConfig - - - true - ManageHealthCheck - - - true - ManageHubConnections - - - true - ManageInteraction - - - true - ManageInternalUsers - - - true - ManageIpAddresses - - - true - ManageKnowledge - - - true - ManageKnowledgeImportExport - - - true - ManageLeads - - - true - ManageLoginAccessPolicies - - - true - ManageMobile - - - true - ManageNetworks - - - true - ManagePackageLicenses - - - true - ManagePasswordPolicies - - - true - ManageProfilesPermissionsets - - - true - ManagePropositions - - - true - ManagePvtRptsAndDashbds - - - true - ManageRecommendationStrategies - - - true - ManageReleaseUpdates - - - true - ManageRemoteAccess - - - true - ManageReportsInPubFolders - - - true - ManageRoles - - - true - ManageSearchPromotionRules - - - true - ManageSharing - - - true - ManageSolutions - - - true - ManageSubscriptions - - - true - ManageSynonyms - - - true - ManageTrustMeasures - - - true - ManageUnlistedGroups - - - true - ManageUsers - - - true - MassInlineEdit - - - true - MergeTopics - - - true - ModerateChatter - - - true - ModifyAllData - - - true - ModifyDataClassification - - - true - ModifyMetadata - - - true - NewReportBuilder - - - true - OutboundMigrationToolsUser - - - true - Packaging2 - - - true - Packaging2Delete - - - true - PrivacyDataAccess - - - true - RemoveDirectMessageMembers - - - true - ResetPasswords - - - true - RunReports - - - true - ScheduleJob - - - true - ScheduleReports - - - true - SelectFilesFromSalesforce - - - true - SendCustomNotifications - - - true - SendSitRequests - - - true - ShareInternalArticles - - - true - ShowCompanyNameAsUserBadge - - - true - SolutionImport - - - true - SubmitMacrosAllowed - - - true - SubscribeDashboardRolesGrps - - - true - SubscribeDashboardToOtherUsers - - - true - SubscribeReportRolesGrps - - - true - SubscribeReportToOtherUsers - - - true - SubscribeReportsRunAsUser - - - true - SubscribeToLightningDashboards - - - true - SubscribeToLightningReports - - - true - TransactionalEmailSend - - - true - TransferAnyCase - - - true - TransferAnyEntity - - - true - TransferAnyLead - - - true - UseTeamReassignWizards - - - true - UseWebLink - - - true - ViewAllData - - - true - ViewAllProfiles - - - true - ViewAllUsers - - - true - ViewDataAssessment - - - true - ViewDataCategories - - - true - ViewDeveloperName - - - true - ViewEventLogFiles - - - true - ViewFlowUsageAndFlowEventData - - - true - ViewHealthCheck - - - true - ViewHelpLink - - - true - ViewMyTeamsDashboards - - - true - ViewPublicDashboards - - - true - ViewPublicReports - - - true - ViewRoles - - - true - ViewSetup - - - true - ViewTrustMeasures - - - true - ViewUserPII - diff --git a/package.json b/package.json index 7f9429323..bd6cee0a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nebula-logger", - "version": "4.7.5", + "version": "4.7.6", "description": "Designed for Salesforce admins, developers & architects. A robust logger for Apex, Flow, Process Builder & Integrations.", "author": "Jonathan Gillespie", "license": "MIT", diff --git a/sfdx-project.json b/sfdx-project.json index eedaf7681..91e49d1c7 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -12,9 +12,9 @@ "package": "Nebula Logger - Core", "path": "./nebula-logger/core", "definitionFile": "./config/scratch-orgs/base-scratch-def.json", - "versionNumber": "4.7.5.NEXT", - "versionName": "Configurable Logger.setScenario() Behavior", - "versionDescription": "Added new LoggerParameter__mdt record UseFirstSpecifiedScenario to control if the first or last scenario specified is used", + "versionNumber": "4.7.6.NEXT", + "versionName": "Support for Component Logging of Apex Controller Errors", + "versionDescription": "Added support for logging in LWC/Aura of error objects returned from Apex controller methods (which have a different structure from a native JavaScript Error)", "releaseNotesUrl": "https://github.com/jongpie/NebulaLogger/releases", "default": true }, @@ -125,7 +125,8 @@ "Nebula Logger - Core@4.7.2-1-parent-log-transaction-id-bugfix": "04t5Y0000015lhdQAA", "Nebula Logger - Core@4.7.3-2-query-selector-classes": "04t5Y0000015liHQAQ", "Nebula Logger - Core@4.7.4-3-new-method-logger.logdatabaseerrors()": "04t5Y0000015ligQAA", - "Nebula Logger - Core@4.7.5-NEXT-configurable-logger.setscenario()-behavior": "04t5Y0000015lkcQAA", + "Nebula Logger - Core@4.7.5-3-configurable-logger.setscenario()-behavior": "04t5Y0000015lkcQAA", + "Nebula Logger - Core@4.7.6-NEXT-support-for-component-logging-of-apex-controller-errors": "04t5Y0000015lkmQAA", "Nebula Logger - Plugin - Async Failure Additions": "0Ho5Y000000blO4SAI", "Nebula Logger - Plugin - Async Failure Additions@1.0.0-3": "04t5Y0000015lhiQAA", "Nebula Logger - Plugin - Async Failure Additions@1.0.1-1": "04t5Y0000015lhsQAA",