From ef8c1ed7777ac2e2a47a13d8585bc06d4503355b Mon Sep 17 00:00:00 2001 From: Jonathan Gillespie Date: Sun, 27 Oct 2024 14:25:36 -0400 Subject: [PATCH 1/3] Added more details in logger LWC to the component log entry JSON that's printed using console statements The stringified object now includes more details, such as the exception and tags Calling the console function now happens with a delay (using setTimeout()) so that additional details can be added to the log entry (using the builder methods) before the log entry is stringified & printed out --- README.md | 2 +- .../main/logger-engine/classes/Logger.cls | 2 +- .../lwc/logger/logEntryBuilder.js | 31 ++++++++-- .../logger-engine/lwc/logger/loggerService.js | 57 +++++++++++++------ .../loggerLWCCreateLoggerImportDemo.js | 2 +- .../loggerLWCGetLoggerImportDemo.js | 3 +- package.json | 2 +- sfdx-project.json | 6 +- 8 files changed, 76 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 4cb3cfc14..500e8d72f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ The most robust observability solution for Salesforce experts. Built 100% natively on the platform, and designed to work seamlessly with Apex, Lightning Components, Flow, OmniStudio, and integrations. -## Unlocked Package - v4.14.16 +## Unlocked Package - v4.14.17 [![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015ocHQAQ) [![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015ocHQAQ) diff --git a/nebula-logger/core/main/logger-engine/classes/Logger.cls b/nebula-logger/core/main/logger-engine/classes/Logger.cls index 515c713da..511bf659d 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.14.16'; + private static final String CURRENT_VERSION_NUMBER = 'v4.14.17'; private static final System.LoggingLevel FALLBACK_LOGGING_LEVEL = System.LoggingLevel.DEBUG; private static final List LOG_ENTRIES_BUFFER = new List(); private static final String MISSING_SCENARIO_ERROR_MESSAGE = 'No logger scenario specified. A scenario is required for logging in this org.'; 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 563a35425..06f0cd55b 100644 --- a/nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js +++ b/nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js @@ -106,8 +106,29 @@ export default class LogEntryEventBuilder { this.#componentLogEntry.error = {}; if (exception.body) { this.#componentLogEntry.error.message = exception.body.message; - this.#componentLogEntry.error.stackTrace = exception.body.stackTrace; this.#componentLogEntry.error.type = exception.body.exceptionType; + + const transformedErrorStackTrace = { + className: undefined, + methodName: undefined, + metadataType: undefined, + triggerName: undefined, + stackTraceString: exception.body.stackTrace + }; + if (exception.body.stackTrace?.indexOf(':') > -1) { + const stackTracePieces = exception.body.stackTrace.split(':')[0].split('.'); + + if (stackTracePieces[0] === 'Class') { + transformedErrorStackTrace.className = stackTracePieces[1]; + transformedErrorStackTrace.methodName = stackTracePieces[stackTracePieces.length - 1]; + transformedErrorStackTrace.metadataType = 'ApexClass'; + } else if (stackTracePieces[0] === 'Trigger') { + transformedErrorStackTrace.triggerName = stackTracePieces[1]; + transformedErrorStackTrace.metadataType = 'ApexTrigger'; + } + } + + this.#componentLogEntry.error.stackTrace = transformedErrorStackTrace; } else { this.#componentLogEntry.error.message = exception.message; this.#componentLogEntry.error.stackTrace = new LoggerStackTrace().parse(exception); @@ -154,9 +175,11 @@ export default class LogEntryEventBuilder { * @return {LogEntryBuilder} The same instance of `LogEntryBuilder`, useful for chaining methods */ addTag(tag) { - this.#componentLogEntry.tags.push(tag); - // Deduplicate the list of tags - this.#componentLogEntry.tags = Array.from(new Set(this.#componentLogEntry.tags)); + if (tag?.trim()) { + this.#componentLogEntry.tags.push(tag?.trim()); + // Deduplicate the list of tags + this.#componentLogEntry.tags = Array.from(new Set(this.#componentLogEntry.tags)); + } return this; } diff --git a/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js b/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js index 4cbcb0a94..3c9f1aea3 100644 --- a/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js +++ b/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js @@ -10,7 +10,7 @@ import LoggerServiceTaskQueue from './loggerServiceTaskQueue'; import getSettings from '@salesforce/apex/ComponentLogger.getSettings'; import saveComponentLogEntries from '@salesforce/apex/ComponentLogger.saveComponentLogEntries'; -const CURRENT_VERSION_NUMBER = 'v4.14.16'; +const CURRENT_VERSION_NUMBER = 'v4.14.17'; const CONSOLE_OUTPUT_CONFIG = { messagePrefix: `%c Nebula Logger ${CURRENT_VERSION_NUMBER} `, @@ -195,7 +195,11 @@ export default class LoggerService { this.#componentLogEntries.push(logEntry); if (this.#settings.isConsoleLoggingEnabled) { - this._logToConsole(logEntry.loggingLevel, logEntry.message, logEntry); + // Use setTimeout() so any extra fields included in the log entry are added first before printing to the console + // eslint-disable-next-line @lwc/lwc/no-async-operation + setTimeout(() => { + this._logToConsole(logEntry.loggingLevel, logEntry.message, logEntry); + }, 1000); } if (this.#settings.isLightningLoggerEnabled) { lightningLog(logEntry); @@ -218,22 +222,41 @@ export default class LoggerService { const consoleLoggingFunction = console[loggingLevel.toLowerCase()] ?? console.debug; const loggingLevelEmoji = LOGGING_LEVEL_EMOJIS[loggingLevel]; const qualifiedMessage = `${loggingLevelEmoji} ${loggingLevel}: ${message}`; - const formattedComponentLogEntryString = !componentLogEntry - ? '' - : '\n' + - JSON.stringify( - { - origin: { - component: componentLogEntry.originStackTrace?.componentName, - function: componentLogEntry.originStackTrace?.functionName, - metadataType: componentLogEntry.originStackTrace?.metadataType - }, - scenario: componentLogEntry.scenario, - timestamp: componentLogEntry.timestamp + // Clean up some extra properties for readability + console.debug('>>> original componentLogEntry: ', JSON.stringify(componentLogEntry, null, 2)); + const simplifiedLogEntry = !componentLogEntry + ? undefined + : { + customFieldMappings: componentLogEntry.fieldToValue.length === 0 ? undefined : componentLogEntry.fieldToValue, + originSource: { + metadataType: componentLogEntry.originStackTrace?.metadataType, + componentName: componentLogEntry.originStackTrace?.componentName, + functionName: componentLogEntry.originStackTrace?.functionName }, - (_, value) => value ?? undefined, - 2 - ); + error: componentLogEntry.error, + scenario: componentLogEntry.scenario, + tags: componentLogEntry.tags.length === 0 ? undefined : componentLogEntry.tags, + timestamp: !componentLogEntry.timestamp + ? undefined + : { + local: new Date(componentLogEntry.timestamp).toLocaleString(), + utc: componentLogEntry.timestamp + } + }; + if (simplifiedLogEntry?.error?.stackTrace) { + simplifiedLogEntry.error.errorSource = { + metadataType: simplifiedLogEntry.error.stackTrace.metadataType, + componentName: simplifiedLogEntry.error.stackTrace.componentName, + functionName: simplifiedLogEntry.error.stackTrace.functionName, + className: simplifiedLogEntry.error.stackTrace.className, + methodName: simplifiedLogEntry.error.stackTrace.methodName, + triggerName: simplifiedLogEntry.error.stackTrace.triggerName, + stackTraceString: simplifiedLogEntry.error.stackTrace.stackTraceString + }; + delete simplifiedLogEntry.error.stackTrace; + } + + const formattedComponentLogEntryString = !simplifiedLogEntry ? undefined : '\n' + JSON.stringify(simplifiedLogEntry, (_, value) => value ?? undefined, 2); consoleLoggingFunction(CONSOLE_OUTPUT_CONFIG.messagePrefix, CONSOLE_OUTPUT_CONFIG.messageFormatting, qualifiedMessage, formattedComponentLogEntryString); } diff --git a/nebula-logger/recipes/lwc/loggerLWCCreateLoggerImportDemo/loggerLWCCreateLoggerImportDemo.js b/nebula-logger/recipes/lwc/loggerLWCCreateLoggerImportDemo/loggerLWCCreateLoggerImportDemo.js index 5d9c81516..d3e8a9493 100644 --- a/nebula-logger/recipes/lwc/loggerLWCCreateLoggerImportDemo/loggerLWCCreateLoggerImportDemo.js +++ b/nebula-logger/recipes/lwc/loggerLWCCreateLoggerImportDemo/loggerLWCCreateLoggerImportDemo.js @@ -82,6 +82,7 @@ export default class LoggerLWCCreateLoggerImportDemo extends LightningElement { scenarioChange(event) { this.scenario = event.target.value; + this.logger.setScenario(this.scenario); } tagsStringChange(event) { @@ -156,7 +157,6 @@ export default class LoggerLWCCreateLoggerImportDemo extends LightningElement { saveLogExample() { console.log('running saveLog for btn'); - this.logger.setScenario(this.scenario); console.log(this.logger); // this.logger.saveLog('QUEUEABLE'); this.logger.saveLog(); diff --git a/nebula-logger/recipes/lwc/loggerLWCGetLoggerImportDemo/loggerLWCGetLoggerImportDemo.js b/nebula-logger/recipes/lwc/loggerLWCGetLoggerImportDemo/loggerLWCGetLoggerImportDemo.js index 831a16db8..6ffbd1733 100644 --- a/nebula-logger/recipes/lwc/loggerLWCGetLoggerImportDemo/loggerLWCGetLoggerImportDemo.js +++ b/nebula-logger/recipes/lwc/loggerLWCGetLoggerImportDemo/loggerLWCGetLoggerImportDemo.js @@ -19,6 +19,7 @@ export default class LoggerLWCGetLoggerImportDemo extends LightningElement { connectedCallback() { try { + this.logger.setScenario(this.scenario); this.logger.error('test error entry').setField({ SomeLogEntryField__c: 'some text from loggerLWCGetLoggerImportDemo' }); this.logger.warn('test warn entry').setField({ SomeLogEntryField__c: 'some text from loggerLWCGetLoggerImportDemo' }); this.logger.info('test info entry').setField({ SomeLogEntryField__c: 'some text from loggerLWCGetLoggerImportDemo' }); @@ -83,6 +84,7 @@ export default class LoggerLWCGetLoggerImportDemo extends LightningElement { scenarioChange(event) { this.scenario = event.target.value; + this.logger.setScenario(this.scenario); } tagsStringChange(event) { @@ -157,7 +159,6 @@ export default class LoggerLWCGetLoggerImportDemo extends LightningElement { saveLogExample() { console.log('running saveLog for btn'); - this.logger.setScenario(this.scenario); console.log(this.logger); // this.logger.saveLog('QUEUEABLE'); this.logger.saveLog(); diff --git a/package.json b/package.json index 0ab8ba275..69875359a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nebula-logger", - "version": "4.14.16", + "version": "4.14.17", "description": "The most robust logger for Salesforce. Works with Apex, Lightning Components, Flow, Process Builder & Integrations. Designed for Salesforce admins, developers & architects.", "author": "Jonathan Gillespie", "license": "MIT", diff --git a/sfdx-project.json b/sfdx-project.json index a90df6670..efead3fe9 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -9,9 +9,9 @@ "path": "./nebula-logger/core", "definitionFile": "./config/scratch-orgs/base-scratch-def.json", "scopeProfiles": true, - "versionNumber": "4.14.16.NEXT", - "versionName": "CallableLogger Enhancements", - "versionDescription": "Added 3 enhancements to the CallableLogger class (used for OmniStudio logging & loosely-coupled dependencies)\n\n 1. It now automatically appends OmniStudio's input data for OmniScript steps as JSON to the Message__c fields on LogEntryEvent__e and LogEntry__c.\n 2. The 'newEntry' action now also supports setting the parent log transaction ID, using the argument 'parentLogTransactionId'.\n 3. Transaction details are now returned in the output for all actions, including the current transaction ID, the parent log transaction ID, and the Salesforce-generated request ID.", + "versionNumber": "4.14.17.NEXT", + "versionName": "Improved JavaScript Console Output", + "versionDescription": "Added more details to the component log entry JSON that's printed using console statements. The stringified object now includes more details, such as the exception, tags, and scenario.", "releaseNotesUrl": "https://github.com/jongpie/NebulaLogger/releases", "unpackagedMetadata": { "path": "./nebula-logger/extra-tests" From 9aef5bac48792a2f953e256269960aff3f316298 Mon Sep 17 00:00:00 2001 From: Jonathan Gillespie Date: Thu, 31 Oct 2024 16:56:04 -0400 Subject: [PATCH 2/3] Fixed Jest failures caused by setTimeout() not running (thanks to @jamessimone for the suggestion!), and updated some Jest asserts to match the updated output of some console statements --- .../lwc/logger/__tests__/logger.test.js | 24 +++++++++++++------ .../logger-engine/lwc/logger/loggerService.js | 1 - 2 files changed, 17 insertions(+), 8 deletions(-) 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 a026e2d6c..74e27c40f 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 @@ -40,8 +40,9 @@ jest.mock( ); describe('logger lwc recommended sync getLogger() import approach tests', () => { - beforeEach(() => { + beforeAll(() => { disableSystemMessages(); + setTimeout = callbackFunction => callbackFunction(); // One of logger's features (when enabled) is to auto-call the browser's console // so devs can see a log entry easily. But during Jest tests, seeing all of the // console statements is... a bit overwhelming, so the global console functions @@ -651,7 +652,7 @@ describe('logger lwc recommended sync getLogger() import approach tests', () => logEntryBuilder.setExceptionDetails(error); expect(logEntry.error.message).toEqual(error.message); - expect(logEntry.error.stackTrace).toBeTruthy(); + expect(logEntry.error.stackTrace.stackTraceString).toEqual(error.stackTrace); expect(logEntry.error.type).toEqual('JavaScript.TypeError'); }); @@ -679,7 +680,10 @@ describe('logger lwc recommended sync getLogger() import approach tests', () => logEntryBuilder.setExceptionDetails(error); expect(logEntry.error.message).toEqual(error.body.message); - expect(logEntry.error.stackTrace).toBeTruthy(); + expect(logEntry.error.stackTrace.metadataType).toEqual('ApexClass'); + expect(logEntry.error.stackTrace.className).toEqual('SomeApexClass'); + expect(logEntry.error.stackTrace.methodName).toEqual('runSomeMethod'); + expect(logEntry.error.stackTrace.stackTraceString).toEqual(error.body.stackTrace); expect(logEntry.error.type).toEqual(error.body.exceptionType); }); @@ -1253,7 +1257,7 @@ describe('logger lwc deprecated async createLogger() import tests', () => { logEntryBuilder.setError(error); expect(logEntry.error.message).toEqual(error.message); - expect(logEntry.error.stackTrace).toBeTruthy(); + expect(logEntry.error.stackTrace.stackTraceString).toEqual(error.stackTrace); expect(logEntry.error.type).toEqual('JavaScript.TypeError'); }); @@ -1278,7 +1282,10 @@ describe('logger lwc deprecated async createLogger() import tests', () => { logEntryBuilder.setError(error); expect(logEntry.error.message).toEqual(error.body.message); - expect(logEntry.error.stackTrace).toBeTruthy(); + expect(logEntry.error.stackTrace.metadataType).toEqual('ApexClass'); + expect(logEntry.error.stackTrace.className).toEqual('SomeApexClass'); + expect(logEntry.error.stackTrace.methodName).toEqual('runSomeMethod'); + expect(logEntry.error.stackTrace.stackTraceString).toEqual(error.body.stackTrace); expect(logEntry.error.type).toEqual(error.body.exceptionType); }); @@ -1817,7 +1824,7 @@ describe('logger lwc legacy markup tests', () => { logEntryBuilder.setError(error); expect(logEntry.error.message).toEqual(error.message); - expect(logEntry.error.stackTrace).toBeTruthy(); + expect(logEntry.error.stackTrace.stackTraceString).toEqual(error.stackTrace); expect(logEntry.error.type).toEqual('JavaScript.TypeError'); }); @@ -1844,7 +1851,10 @@ describe('logger lwc legacy markup tests', () => { logEntryBuilder.setError(error); expect(logEntry.error.message).toEqual(error.body.message); - expect(logEntry.error.stackTrace).toEqual(error.body.stackTrace); + expect(logEntry.error.stackTrace.metadataType).toEqual('ApexClass'); + expect(logEntry.error.stackTrace.className).toEqual('SomeApexClass'); + expect(logEntry.error.stackTrace.methodName).toEqual('runSomeMethod'); + expect(logEntry.error.stackTrace.stackTraceString).toEqual(error.body.stackTrace); expect(logEntry.error.type).toEqual(error.body.exceptionType); }); diff --git a/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js b/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js index 3c9f1aea3..fa66a9ad9 100644 --- a/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js +++ b/nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js @@ -223,7 +223,6 @@ export default class LoggerService { const loggingLevelEmoji = LOGGING_LEVEL_EMOJIS[loggingLevel]; const qualifiedMessage = `${loggingLevelEmoji} ${loggingLevel}: ${message}`; // Clean up some extra properties for readability - console.debug('>>> original componentLogEntry: ', JSON.stringify(componentLogEntry, null, 2)); const simplifiedLogEntry = !componentLogEntry ? undefined : { From e289dbabce659c3b3fb6701af801fab66b8c3f86 Mon Sep 17 00:00:00 2001 From: GitHub Action Bot Date: Thu, 31 Oct 2024 23:27:53 +0000 Subject: [PATCH 3/3] Created new core unlocked package version --- README.md | 8 ++++---- sfdx-project.json | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 500e8d72f..d6fa004c5 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,13 @@ The most robust observability solution for Salesforce experts. Built 100% native ## Unlocked Package - v4.14.17 -[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015ocHQAQ) -[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015ocHQAQ) +[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015ocRQAQ) +[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015ocRQAQ) [![View Documentation](./images/btn-view-documentation.png)](https://github.com/jongpie/NebulaLogger/wiki) -`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y0000015ocHQAQ` +`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y0000015ocRQAQ` -`sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y0000015ocHQAQ` +`sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y0000015ocRQAQ` --- diff --git a/sfdx-project.json b/sfdx-project.json index efead3fe9..73eeeceb1 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -201,6 +201,7 @@ "Nebula Logger - Core@4.14.14-new-apex-static-method-&-javascript-function-logger.setfield()": "04t5Y0000015oWIQAY", "Nebula Logger - Core@4.14.15-create-log-entry-for-asyncapexcontext": "04t5Y0000015obxQAA", "Nebula Logger - Core@4.14.16-callablelogger-enhancements": "04t5Y0000015ocHQAQ", + "Nebula Logger - Core@4.14.17-improved-javascript-console-output": "04t5Y0000015ocRQAQ", "Nebula Logger - Core Plugin - Async Failure Additions": "0Ho5Y000000blO4SAI", "Nebula Logger - Core Plugin - Async Failure Additions@1.0.0": "04t5Y0000015lhiQAA", "Nebula Logger - Core Plugin - Async Failure Additions@1.0.1": "04t5Y0000015lhsQAA",