Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added more details in logger LWC's console statements #789

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 54 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.14
## Unlocked Package - v4.14.16

[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oWIQAY)
[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oWIQAY)
Expand Down Expand Up @@ -238,41 +238,41 @@ This example batchable class shows how you can leverage this feature to relate a

```apex
public with sharing class BatchableLoggerExample implements Database.Batchable<SObject>, Database.Stateful {
private String originalTransactionId;
private String originalTransactionId;

public Database.QueryLocator start(Database.BatchableContext batchableContext) {
// Each batchable method runs in a separate transaction,
// so store the first transaction ID to later relate the other transactions
this.originalTransactionId = Logger.getTransactionId();
public Database.QueryLocator start(Database.BatchableContext batchableContext) {
// Each batchable method runs in a separate transaction,
// so store the first transaction ID to later relate the other transactions
this.originalTransactionId = Logger.getTransactionId();

Logger.info('Starting BatchableLoggerExample');
Logger.saveLog();
Logger.info('Starting BatchableLoggerExample');
Logger.saveLog();

// Just as an example, query all accounts
return Database.getQueryLocator([SELECT Id, Name, RecordTypeId FROM Account]);
}

public void execute(Database.BatchableContext batchableContext, List<Account> scope) {
// One-time call (per transaction) to set the parent log
Logger.setParentLogTransactionId(this.originalTransactionId);
// Just as an example, query all accounts
return Database.getQueryLocator([SELECT Id, Name, RecordTypeId FROM Account]);
}

for (Account account : scope) {
// Add your batch job's logic here
public void execute(Database.BatchableContext batchableContext, List<Account> scope) {
// One-time call (per transaction) to set the parent log
Logger.setParentLogTransactionId(this.originalTransactionId);

// Then log the result
Logger.info('Processed an account record', account);
}
for (Account account : scope) {
// Add your batch job's logic here

Logger.saveLog();
// Then log the result
Logger.info('Processed an account record', account);
}

public void finish(Database.BatchableContext batchableContext) {
// The finish method runs in yet-another transaction, so set the parent log again
Logger.setParentLogTransactionId(this.originalTransactionId);
Logger.saveLog();
}

Logger.info('Finishing running BatchableLoggerExample');
Logger.saveLog();
}
public void finish(Database.BatchableContext batchableContext) {
// The finish method runs in yet-another transaction, so set the parent log again
Logger.setParentLogTransactionId(this.originalTransactionId);

Logger.info('Finishing running BatchableLoggerExample');
Logger.saveLog();
}
}
```

Expand All @@ -282,42 +282,42 @@ Queueable jobs can also leverage the parent transaction ID to relate logs togeth

```apex
public with sharing class QueueableLoggerExample implements Queueable {
private Integer numberOfJobsToChain;
private String parentLogTransactionId;
private Integer numberOfJobsToChain;
private String parentLogTransactionId;

private List<LogEntryEvent__e> logEntryEvents = new List<LogEntryEvent__e>();
private List<LogEntryEvent__e> logEntryEvents = new List<LogEntryEvent__e>();

// Main constructor - for demo purposes, it accepts an integer that controls how many times the job runs
public QueueableLoggerExample(Integer numberOfJobsToChain) {
this(numberOfJobsToChain, null);
}
// Main constructor - for demo purposes, it accepts an integer that controls how many times the job runs
public QueueableLoggerExample(Integer numberOfJobsToChain) {
this(numberOfJobsToChain, null);
}

// Second constructor, used to pass the original transaction's ID to each chained instance of the job
// You don't have to use a constructor - a public method or property would work too.
// There just needs to be a way to pass the value of parentLogTransactionId between instances
public QueueableLoggerExample(Integer numberOfJobsToChain, String parentLogTransactionId) {
this.numberOfJobsToChain = numberOfJobsToChain;
this.parentLogTransactionId = parentLogTransactionId;
}
// Second constructor, used to pass the original transaction's ID to each chained instance of the job
// You don't have to use a constructor - a public method or property would work too.
// There just needs to be a way to pass the value of parentLogTransactionId between instances
public QueueableLoggerExample(Integer numberOfJobsToChain, String parentLogTransactionId) {
this.numberOfJobsToChain = numberOfJobsToChain;
this.parentLogTransactionId = parentLogTransactionId;
}

// Creates some log entries and starts a new instance of the job when applicable (based on numberOfJobsToChain)
public void execute(System.QueueableContext queueableContext) {
Logger.setParentLogTransactionId(this.parentLogTransactionId);
// Creates some log entries and starts a new instance of the job when applicable (based on numberOfJobsToChain)
public void execute(System.QueueableContext queueableContext) {
Logger.setParentLogTransactionId(this.parentLogTransactionId);

Logger.fine('queueableContext==' + queueableContext);
Logger.info('this.numberOfJobsToChain==' + this.numberOfJobsToChain);
Logger.info('this.parentLogTransactionId==' + this.parentLogTransactionId);
Logger.fine('queueableContext==' + queueableContext);
Logger.info('this.numberOfJobsToChain==' + this.numberOfJobsToChain);
Logger.info('this.parentLogTransactionId==' + this.parentLogTransactionId);

// Add your queueable job's logic here
// Add your queueable job's logic here

Logger.saveLog();
Logger.saveLog();

--this.numberOfJobsToChain;
if (this.numberOfJobsToChain > 0) {
String parentLogTransactionId = this.parentLogTransactionId != null ? this.parentLogTransactionId : Logger.getTransactionId();
System.enqueueJob(new QueueableLoggerExample(this.numberOfJobsToChain, parentLogTransactionId));
}
--this.numberOfJobsToChain;
if (this.numberOfJobsToChain > 0) {
String parentLogTransactionId = this.parentLogTransactionId != null ? this.parentLogTransactionId : Logger.getTransactionId();
System.enqueueJob(new QueueableLoggerExample(this.numberOfJobsToChain, parentLogTransactionId));
}
}
}
```

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.14';
private static final String CURRENT_VERSION_NUMBER = 'v4.14.16';
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 @@ -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
57 changes: 40 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.14';
const CURRENT_VERSION_NUMBER = 'v4.14.16';

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,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);
}
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.14",
"version": "4.14.16",
"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
6 changes: 3 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.14.NEXT",
"versionName": "New Apex Static Method & JavaScript Function Logger.setField()",
"versionDescription": "Added a new Apex static method Logger.setField() and LWC function logger.setField() so custom fields can be set once --> auto-populated on all subsequent LogEntryEvent__e records",
"versionNumber": "4.14.16.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
Loading