Skip to content

Commit

Permalink
Added more details in logger LWC's console statements (#789)
Browse files Browse the repository at this point in the history
* Added more details in logger LWC to the component log entry JSON that's printed using console statements, 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
  • Loading branch information
jongpie authored Nov 1, 2024
1 parent 1707c41 commit e58b02f
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 40 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

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)
[![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`

---

Expand Down
2 changes: 1 addition & 1 deletion nebula-logger/core/main/logger-engine/classes/Logger.cls
Original file line number Diff line number Diff line change
Expand Up @@ -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<LogEntryEventBuilder> LOG_ENTRIES_BUFFER = new List<LogEntryEventBuilder>();
private static final String MISSING_SCENARIO_ERROR_MESSAGE = 'No logger scenario specified. A scenario is required for logging in this org.';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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');
});

Expand Down Expand Up @@ -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);
});

Expand Down Expand Up @@ -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');
});

Expand All @@ -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);
});

Expand Down Expand Up @@ -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');
});

Expand All @@ -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);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}

Expand Down
56 changes: 39 additions & 17 deletions nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js
Original file line number Diff line number Diff line change
Expand Up @@ -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} `,
Expand Down Expand Up @@ -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);
Expand All @@ -218,22 +222,40 @@ 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
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export default class LoggerLWCCreateLoggerImportDemo extends LightningElement {

scenarioChange(event) {
this.scenario = event.target.value;
this.logger.setScenario(this.scenario);
}

tagsStringChange(event) {
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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' });
Expand Down Expand Up @@ -83,6 +84,7 @@ export default class LoggerLWCGetLoggerImportDemo extends LightningElement {

scenarioChange(event) {
this.scenario = event.target.value;
this.logger.setScenario(this.scenario);
}

tagsStringChange(event) {
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
7 changes: 4 additions & 3 deletions sfdx-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -201,6 +201,7 @@
"Nebula Logger - [email protected]&-javascript-function-logger.setfield()": "04t5Y0000015oWIQAY",
"Nebula Logger - [email protected]": "04t5Y0000015obxQAA",
"Nebula Logger - [email protected]": "04t5Y0000015ocHQAQ",
"Nebula Logger - [email protected]": "04t5Y0000015ocRQAQ",
"Nebula Logger - Core Plugin - Async Failure Additions": "0Ho5Y000000blO4SAI",
"Nebula Logger - Core Plugin - Async Failure [email protected]": "04t5Y0000015lhiQAA",
"Nebula Logger - Core Plugin - Async Failure [email protected]": "04t5Y0000015lhsQAA",
Expand Down

0 comments on commit e58b02f

Please sign in to comment.