Skip to content

Commit

Permalink
Added JavaScript support for setting fields once per component instan…
Browse files Browse the repository at this point in the history
…ce, using logger.setField()
  • Loading branch information
jongpie committed Oct 11, 2024
1 parent 576fdf2 commit 9d437dd
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 38 deletions.
55 changes: 41 additions & 14 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.13
## Unlocked Package - v4.14.14

[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oW3QAI)
[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oW3QAI)
Expand Down Expand Up @@ -657,28 +657,55 @@ The first step is to add a field to the platform event `LogEntryEvent__e`

![Custom Field on LogEntryEvent__e](./images/custom-field-log-entry-event.png)

- In Apex, populate your field(s) by calling the instance method overloads `LogEntryEventBuilder.setField(Schema.SObjectField field, Object fieldValue)` or `LogEntryEventBuilder.setField(Map<Schema.SObjectField, Object> fieldToValue)`
- In Apex, you have 2 ways to populate your custom fields

```apex Logger.info('hello, world')
// Set a single field
.setField(LogEntryEvent__e.SomeCustomTextField__c, 'some text value')
// Set multiple fields
.setField(new Map<Schema.SObjectField, Object>{
LogEntryEvent__e.AnotherCustomTextField__c => 'another text value',
LogEntryEvent__e.SomeCustomDatetimeField__c => System.now()
});
1. Set the field once per transaction - every `LogEntryEvent__e` logged in the transaction will then automatically have the specified field populated with the same value.
- This is typically used for fields that are mapped to an equivalent `Log__c` or `LoggerScenario__c` field.

- How: call the static method overloads `Logger.setField(Schema.SObjectField field, Object fieldValue)` or `Logger.setField(Map<Schema.SObjectField, Object> fieldToValue)`

2. Set the field on a specific `LogEntryEvent__e` record - other records will not have the field automatically set.
- This is typically used for fields that are mapped to an equivalent `LogEntry__c` field.
- How: call the instance method overloads `LogEntryEventBuilder.setField(Schema.SObjectField field, Object fieldValue)` or `LogEntryEventBuilder.setField(Map<Schema.SObjectField, Object> fieldToValue)`

```apex
// Set My_Field__c on every log entry event created in this transaction with the same value
Logger.setField(LogEntryEvent__e.My_Field__c, 'some value that applies to the whole Apex transaction');
// Set fields on specific entries
Logger.warn('hello, world - "a value" set for Some_Other_Field__c').setField(LogEntryEvent__e.Some_Other_Field__c, 'a value')
Logger.warn('hello, world - "different value" set for Some_Other_Field__c').setField(LogEntryEvent__e.Some_Other_Field__c, 'different value')
Logger.info('hello, world - no value set for Some_Other_Field__c');
Logger.saveLog();
```

- In JavaScript, populate your field(s) by calling the instance function `LogEntryEventBuilder.setField(Object fieldToValue)`
- In JavaScript, you have 2 ways to populate your custom fields. These are very similar to the 2 ways available in Apex (above).

1. Set the field once per component - every `LogEntryEvent__e` logged in your component will then automatically have the specified field populated with the same value.
- This is typically used for fields that are mapped to an equivalent `Log__c` or `LoggerScenario__c` field.

- How: call the `logger` LWC function `logger.setField(Object fieldToValue)`

2. Set the field on a specific `LogEntryEvent__e` record - other records will not have the field automatically set.
- This is typically used for fields that are mapped to an equivalent `LogEntry__c` field.
- How: call the instance function `LogEntryEventBuilder.setField(Object fieldToValue)`

```javascript
import { getLogger } from 'c/logger';

export default class loggerLWCGetLoggerImportDemo extends LightningElement {
export default class LoggerLWCImportDemo extends LightningElement {
logger = getLogger();

async connectedCallback() {
this.logger.info('Hello, world').setField({ SomeCustomTextField__c: 'some text value', SomeCustomNumbertimeField__c: 123 });
connectedCallback() {
// Set My_Field__c on every log entry event created in this component with the same value
this.logger.setField(LogEntryEvent__e.My_Field__c, 'some value that applies to any subsequent entry');

// Set fields on specific entries
this.logger.warn('hello, world - "a value" set for Some_Other_Field__c').setField({ Some_Other_Field__c: 'a value' });
this.logger.warn('hello, world - "different value" set for Some_Other_Field__c').setField({ Some_Other_Field__c: 'different value' });
this.logger.info('hello, world - no value set for Some_Other_Field__c');

this.logger.saveLog();
}
}
Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ module.exports = {
'^lightning/empApi$': '<rootDir>/config/jest/mocks/lightning/empApi',
'^lightning/navigation$': '<rootDir>/config/jest/mocks/lightning/navigation'
},
modulePathIgnorePatterns: ['recipes'],
// modulePathIgnorePatterns: ['recipes'],
testPathIgnorePatterns: ['<rootDir>/temp/']
};
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.13';
private static final String CURRENT_VERSION_NUMBER = 'v4.14.14';
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 @@ -543,7 +543,6 @@ describe('logger lwc recommended sync getLogger() import approach tests', () =>
// getLogger() is built to be sync, but internally, some async tasks must execute
// before some sync tasks are executed
await flushPromises('Resolve async task queue');
await logger.getUserSettings();

const logEntry = logger.info('example log entry').getComponentLogEntry();

Expand All @@ -555,13 +554,36 @@ describe('logger lwc recommended sync getLogger() import approach tests', () =>
expect(logEntry.browser.windowResolution).toEqual(window.innerWidth + ' x ' + window.innerHeight);
});

it('sets multiple custom fields', async () => {
it('sets multiple custom component fields on subsequent entries', async () => {
getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS });
const logger = getLogger();
// getLogger() is built to be sync, but internally, some async tasks must execute
// before some sync tasks are executed
await flushPromises('Resolve async task queue');
const firstFakeFieldName = 'SomeField__c';
const firstFieldMockValue = 'something';
const secondFakeFieldName = 'AnotherField__c';
const secondFieldMockValue = 'another value';

const previousLogEntry = logger.info('example log entry from before setField() is called').getComponentLogEntry();
logger.setField({
[firstFakeFieldName]: firstFieldMockValue,
[secondFakeFieldName]: secondFieldMockValue
});
const subsequentLogEntry = logger.info('example log entry from after setField() is called').getComponentLogEntry();

expect(previousLogEntry.fieldToValue[firstFakeFieldName]).toBeUndefined();
expect(previousLogEntry.fieldToValue[secondFakeFieldName]).toBeUndefined();
expect(subsequentLogEntry.fieldToValue[firstFakeFieldName]).toEqual(firstFieldMockValue);
expect(subsequentLogEntry.fieldToValue[secondFakeFieldName]).toEqual(secondFieldMockValue);
});

it('sets multiple custom entry fields on a single entry', async () => {
getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS });
const logger = getLogger();
// getLogger() is built to be sync, but internally, some async tasks must execute
// before some sync tasks are executed
await flushPromises('Resolve async task queue');
await logger.getUserSettings();
const logEntryBuilder = logger.info('example log entry');
const logEntry = logEntryBuilder.getComponentLogEntry();
const firstFakeFieldName = 'SomeField__c';
Expand All @@ -586,7 +608,6 @@ describe('logger lwc recommended sync getLogger() import approach tests', () =>
// getLogger() is built to be sync, but internally, some async tasks must execute
// before some sync tasks are executed
await flushPromises('Resolve async task queue');
await logger.getUserSettings();
const logEntryBuilder = logger.info('example log entry');
const logEntry = logEntryBuilder.getComponentLogEntry();
expect(logEntry.recordId).toBeFalsy();
Expand All @@ -603,7 +624,6 @@ describe('logger lwc recommended sync getLogger() import approach tests', () =>
// getLogger() is built to be sync, but internally, some async tasks must execute
// before some sync tasks are executed
await flushPromises('Resolve async task queue');
await logger.getUserSettings();
const logEntryBuilder = logger.info('example log entry');
const logEntry = logEntryBuilder.getComponentLogEntry();
expect(logEntry.record).toBeFalsy();
Expand All @@ -620,7 +640,6 @@ describe('logger lwc recommended sync getLogger() import approach tests', () =>
// getLogger() is built to be sync, but internally, some async tasks must execute
// before some sync tasks are executed
await flushPromises('Resolve async task queue');
await logger.getUserSettings();
const logEntryBuilder = logger.info('example log entry');
const logEntry = logEntryBuilder.getComponentLogEntry();
expect(logEntry.error).toBeFalsy();
Expand Down Expand Up @@ -1147,7 +1166,29 @@ describe('logger lwc deprecated async createLogger() import tests', () => {
expect(logEntry.browser.windowResolution).toEqual(window.innerWidth + ' x ' + window.innerHeight);
});

it('sets multiple custom fields when using deprecated async createLogger() import approach', async () => {
it('sets multiple custom component fields on subsequent entries when using deprecated async createLogger() import approach', async () => {
getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS });
const logger = await createLogger();
await logger.getUserSettings();
const firstFakeFieldName = 'SomeField__c';
const firstFieldMockValue = 'something';
const secondFakeFieldName = 'AnotherField__c';
const secondFieldMockValue = 'another value';

const previousLogEntry = logger.info('example log entry from before setField() is called').getComponentLogEntry();
logger.setField({
[firstFakeFieldName]: firstFieldMockValue,
[secondFakeFieldName]: secondFieldMockValue
});
const subsequentLogEntry = logger.info('example log entry from after setField() is called').getComponentLogEntry();

expect(previousLogEntry.fieldToValue[firstFakeFieldName]).toBeUndefined();
expect(previousLogEntry.fieldToValue[secondFakeFieldName]).toBeUndefined();
expect(subsequentLogEntry.fieldToValue[firstFakeFieldName]).toEqual(firstFieldMockValue);
expect(subsequentLogEntry.fieldToValue[secondFakeFieldName]).toEqual(secondFieldMockValue);
});

it('sets multiple custom entry fields on a single entry when using deprecated async createLogger() import approach', async () => {
getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS });
const logger = await createLogger();
await logger.getUserSettings();
Expand Down
9 changes: 9 additions & 0 deletions nebula-logger/core/main/logger-engine/lwc/logger/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ export default class Logger extends LightningElement {
return this.#loggerService.getUserSettings();
}

/**
* @description Sets multiple field values on the builder's `LogEntryEvent__e` record
* @param {Object} fieldToValue An object containing the custom field name as a key, with the corresponding value to store.
* Example: `{"SomeField__c": "some value", "AnotherField__c": "another value"}`
*/
setField(fieldToValue) {
this.#loggerService.setField(fieldToValue);
}

/**
* @description Sets the scenario name for the current transaction - this is stored in `LogEntryEvent__e.Scenario__c`
* and `Log__c.Scenario__c`, and can be used to filter & group logs
Expand Down
13 changes: 13 additions & 0 deletions nebula-logger/core/main/logger-engine/lwc/logger/loggerService.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export class BrowserContext {
export default class LoggerService {
static hasInitialized = false;

#componentFieldToValue = {};
#componentLogEntries = [];
#settings;
#scenario;
Expand All @@ -69,6 +70,17 @@ export default class LoggerService {
return this.#settings;
}

/**
* @description Sets multiple field values on every generated `LogEntryEvent__e` record
* @param {Object} fieldToValue An object containing the custom field name as a key, with the corresponding value to store.
* Example: `{"SomeField__c": "some value", "AnotherField__c": "another value"}`
*/
setField(fieldToValue) {
if (!!fieldToValue && typeof fieldToValue === 'object' && !Array.isArray(fieldToValue)) {
Object.assign(this.#componentFieldToValue, fieldToValue);
}
}

setScenario(scenario) {
this.#scenario = scenario;
}
Expand Down Expand Up @@ -176,6 +188,7 @@ export default class LoggerService {
.setMessage(message)
.setScenario(this.#scenario);
const logEntry = logEntryBuilder.getComponentLogEntry();
Object.assign(logEntry.fieldToValue, this.#componentFieldToValue);

const loggingLevelCheckTask = providedLoggingLevel => {
if (this._meetsUserLoggingLevel(providedLoggingLevel)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createElement } from 'lwc';
import { getLogger } from 'c/logger';
import loggerLWCCreateLoggerImportDemo from 'c/loggerLWCCreateLoggerImportDemo';

import getSettings from '@salesforce/apex/ComponentLogger.getSettings';
Expand All @@ -15,6 +16,10 @@ const MOCK_GET_SETTINGS = {
userLoggingLevel: { ordinal: 2, name: 'FINEST' }
};

jest.mock('lightning/logger', () => ({ log: jest.fn() }), {
virtual: true
});

jest.mock(
'@salesforce/apex/ComponentLogger.getSettings',
() => {
Expand All @@ -36,10 +41,11 @@ describe('logger demo tests', () => {
it('mounts and saves log correctly in one go', async () => {
getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS });
const demo = createElement('c-logger-demo', { is: loggerLWCCreateLoggerImportDemo });
document.body.appendChild(demo);

await flushPromises();
document.body.appendChild(demo);
await flushPromises('Resolve async tasks from mounting component');

expect(demo.logger?.getBufferSize()).toBe(0);
expect(demo.logger).toBeDefined();
expect(demo.logger.getBufferSize()).toBe(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@
//------------------------------------------------------------------------------------------------//

/* eslint-disable no-console */
import { LightningElement, wire } from 'lwc';
import { LightningElement, api, wire } from 'lwc';
import returnSomeString from '@salesforce/apex/LoggerLWCDemoController.returnSomeString';
import throwSomeError from '@salesforce/apex/LoggerLWCDemoController.throwSomeError';
import { createLogger } from 'c/logger';

export default class LoggerLWCCreateLoggerImportDemo extends LightningElement {
someBoolean = false;
@api logger;

message = 'Hello, world!';
scenario = 'Some demo scenario';
someBoolean = false;
tagsString = 'Tag-one, Another tag here';
logger;

async connectedCallback() {
/* eslint-disable-next-line @lwc/lwc/no-api-reassignments */
this.logger = await createLogger();
console.log('>>> start of connectedCallback()');
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const MOCK_GET_SETTINGS = {
userLoggingLevel: { ordinal: 2, name: 'FINEST' }
};

jest.mock('lightning/logger', () => ({ log: jest.fn() }), {
virtual: true
});

jest.mock(
'@salesforce/apex/ComponentLogger.getSettings',
() => {
Expand All @@ -36,10 +40,11 @@ describe('logger demo tests', () => {
it('mounts and saves log correctly in one go', async () => {
getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS });
const demo = createElement('c-logger-demo', { is: loggerLWCGetLoggerImportDemo });
document.body.appendChild(demo);

await flushPromises();
document.body.appendChild(demo);
await flushPromises('Resolve async tasks from mounting component');

expect(demo.logger?.getBufferSize()).toBe(0);
expect(demo.logger).toBeDefined();
expect(demo.logger.getBufferSize()).toBe(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@
//------------------------------------------------------------------------------------------------//

/* eslint-disable no-console */
import { LightningElement, wire } from 'lwc';
import { LightningElement, api, wire } from 'lwc';
import returnSomeString from '@salesforce/apex/LoggerLWCDemoController.returnSomeString';
import throwSomeError from '@salesforce/apex/LoggerLWCDemoController.throwSomeError';
import { getLogger } from 'c/logger';

export default class LoggerLWCGetLoggerImportDemo extends LightningElement {
someBoolean = false;
@api logger = getLogger();

message = 'Hello, world!';
scenario = 'Some demo scenario';
someBoolean = false;
tagsString = 'Tag-one, Another tag here';
logger = getLogger();

connectedCallback() {
try {
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.13",
"version": "4.14.14",
"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
4 changes: 2 additions & 2 deletions sfdx-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"definitionFile": "./config/scratch-orgs/base-scratch-def.json",
"scopeProfiles": true,
"versionNumber": "4.14.14.NEXT",
"versionName": "New Apex Static Method Logger.setField()",
"versionDescription": "Added a new Apex static method Logger.setField() so custom fields can be set once per transaction --> auto-populated on all LogEntryEvent__e records",
"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",
"releaseNotesUrl": "https://github.com/jongpie/NebulaLogger/releases",
"unpackagedMetadata": {
"path": "./nebula-logger/extra-tests"
Expand Down

0 comments on commit 9d437dd

Please sign in to comment.