Skip to content

Commit 5453428

Browse files
authored
Added new Apex method & JavaScript function Logger.setField() (#772)
* Added a new Apex static method Logger.setField() so custom fields can be set once per transaction --> auto-populated on all subsequent LogEntryEvent__e records * Added new JavaScript function logger.setField() so custom fields can be set once per component instance --> auto-populated on all subsequent LogEntryEvent__e records
1 parent ac1e38c commit 5453428

16 files changed

Lines changed: 277 additions & 48 deletions

File tree

README.md

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55

66
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.
77

8-
## Unlocked Package - v4.14.13
8+
## Unlocked Package - v4.14.14
99

10-
[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oW3QAI)
11-
[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oW3QAI)
10+
[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oWIQAY)
11+
[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oWIQAY)
1212
[![View Documentation](./images/btn-view-documentation.png)](https://github.com/jongpie/NebulaLogger/wiki)
1313

14-
`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y0000015oW3QAI`
14+
`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y0000015oWIQAY`
1515

16-
`sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y0000015oW3QAI`
16+
`sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y0000015oWIQAY`
1717

1818
---
1919

@@ -657,28 +657,55 @@ The first step is to add a field to the platform event `LogEntryEvent__e`
657657

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

660-
- 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)`
660+
- In Apex, you have 2 ways to populate your custom fields
661661

662-
```apex Logger.info('hello, world')
663-
// Set a single field
664-
.setField(LogEntryEvent__e.SomeCustomTextField__c, 'some text value')
665-
// Set multiple fields
666-
.setField(new Map<Schema.SObjectField, Object>{
667-
LogEntryEvent__e.AnotherCustomTextField__c => 'another text value',
668-
LogEntryEvent__e.SomeCustomDatetimeField__c => System.now()
669-
});
662+
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.
663+
- This is typically used for fields that are mapped to an equivalent `Log__c` or `LoggerScenario__c` field.
664+
665+
- How: call the static method overloads `Logger.setField(Schema.SObjectField field, Object fieldValue)` or `Logger.setField(Map<Schema.SObjectField, Object> fieldToValue)`
666+
667+
2. Set the field on a specific `LogEntryEvent__e` record - other records will not have the field automatically set.
668+
- This is typically used for fields that are mapped to an equivalent `LogEntry__c` field.
669+
- How: call the instance method overloads `LogEntryEventBuilder.setField(Schema.SObjectField field, Object fieldValue)` or `LogEntryEventBuilder.setField(Map<Schema.SObjectField, Object> fieldToValue)`
670+
671+
```apex
672+
// Set My_Field__c on every log entry event created in this transaction with the same value
673+
Logger.setField(LogEntryEvent__e.My_Field__c, 'some value that applies to the whole Apex transaction');
674+
675+
// Set fields on specific entries
676+
Logger.warn('hello, world - "a value" set for Some_Other_Field__c').setField(LogEntryEvent__e.Some_Other_Field__c, 'a value')
677+
Logger.warn('hello, world - "different value" set for Some_Other_Field__c').setField(LogEntryEvent__e.Some_Other_Field__c, 'different value')
678+
Logger.info('hello, world - no value set for Some_Other_Field__c');
679+
680+
Logger.saveLog();
670681
```
671682

672-
- In JavaScript, populate your field(s) by calling the instance function `LogEntryEventBuilder.setField(Object fieldToValue)`
683+
- In JavaScript, you have 2 ways to populate your custom fields. These are very similar to the 2 ways available in Apex (above).
684+
685+
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.
686+
- This is typically used for fields that are mapped to an equivalent `Log__c` or `LoggerScenario__c` field.
687+
688+
- How: call the `logger` LWC function `logger.setField(Object fieldToValue)`
689+
690+
2. Set the field on a specific `LogEntryEvent__e` record - other records will not have the field automatically set.
691+
- This is typically used for fields that are mapped to an equivalent `LogEntry__c` field.
692+
- How: call the instance function `LogEntryEventBuilder.setField(Object fieldToValue)`
673693

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

677-
export default class loggerLWCGetLoggerImportDemo extends LightningElement {
697+
export default class LoggerDemo extends LightningElement {
678698
logger = getLogger();
679699

680-
async connectedCallback() {
681-
this.logger.info('Hello, world').setField({ SomeCustomTextField__c: 'some text value', SomeCustomNumbertimeField__c: 123 });
700+
connectedCallback() {
701+
// Set My_Field__c on every log entry event created in this component with the same value
702+
this.logger.setField({My_Field__c, 'some value that applies to any subsequent entry'});
703+
704+
// Set fields on specific entries
705+
this.logger.warn('hello, world - "a value" set for Some_Other_Field__c').setField({ Some_Other_Field__c: 'a value' });
706+
this.logger.warn('hello, world - "different value" set for Some_Other_Field__c').setField({ Some_Other_Field__c: 'different value' });
707+
this.logger.info('hello, world - no value set for Some_Other_Field__c');
708+
682709
this.logger.saveLog();
683710
}
684711
}

docs/apex/Logger-Engine/LogEntryEventBuilder.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ The same instance of `LogEntryEventBuilder`, useful for chaining methods
402402

403403
#### `setField(Schema.SObjectField field, Object fieldValue)``LogEntryEventBuilder`
404404

405-
Sets a field values on the builder&apos;s `LogEntryEvent__e` record
405+
Sets a field value on the builder&apos;s `LogEntryEvent__e` record
406406

407407
##### Parameters
408408

docs/apex/Logger-Engine/Logger.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4948,6 +4948,27 @@ Stores additional details about the current transacation&apos;s async context
49484948
| -------------------- | ------------------------------------------------------ |
49494949
| `schedulableContext` | - The instance of `System.SchedulableContext` to track |
49504950

4951+
#### `setField(Schema.SObjectField field, Object fieldValue)``void`
4952+
4953+
Sets a field value on every generated `LogEntryEvent__e` record
4954+
4955+
##### Parameters
4956+
4957+
| Param | Description |
4958+
| ------------ | -------------------------------------------------------- |
4959+
| `field` | The `Schema.SObjectField` token of the field to populate |
4960+
| `fieldValue` | The `Object` value to populate in the provided field |
4961+
4962+
#### `setField(Map<Schema.SObjectField, Object> fieldToValue)``void`
4963+
4964+
Sets multiple field values oon every generated `LogEntryEvent__e` record
4965+
4966+
##### Parameters
4967+
4968+
| Param | Description |
4969+
| -------------- | ---------------------------------------------------------------------- |
4970+
| `fieldToValue` | An instance of `Map&lt;Schema.SObjectField, Object&gt;` containing the |
4971+
49514972
#### `setParentLogTransactionId(String parentTransactionId)``void`
49524973

49534974
Relates the current transaction&apos;s log to a parent log via the field Log**c.ParentLog**c This is useful for relating multiple asynchronous operations together, such as batch &amp; queueable jobs.

jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ module.exports = {
66
'^lightning/empApi$': '<rootDir>/config/jest/mocks/lightning/empApi',
77
'^lightning/navigation$': '<rootDir>/config/jest/mocks/lightning/navigation'
88
},
9-
modulePathIgnorePatterns: ['recipes'],
9+
// modulePathIgnorePatterns: ['recipes'],
1010
testPathIgnorePatterns: ['<rootDir>/temp/']
1111
};

nebula-logger/core/main/logger-engine/classes/LogEntryEventBuilder.cls

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ global with sharing class LogEntryEventBuilder {
629629
}
630630

631631
/**
632-
* @description Sets a field values on the builder's `LogEntryEvent__e` record
632+
* @description Sets a field value on the builder's `LogEntryEvent__e` record
633633
* @param field The `Schema.SObjectField` token of the field to populate
634634
* on the builder's `LogEntryEvent__e` record
635635
* @param fieldValue The `Object` value to populate in the provided field
@@ -646,7 +646,7 @@ global with sharing class LogEntryEventBuilder {
646646
/**
647647
* @description Sets multiple field values on the builder's `LogEntryEvent__e` record
648648
* @param fieldToValue An instance of `Map<Schema.SObjectField, Object>` containing the
649-
* the fields & values to populate the builder's `LogEntryEvent__e` record
649+
* the fields & values to populate on the builder's `LogEntryEvent__e` record
650650
* @return The same instance of `LogEntryEventBuilder`, useful for chaining methods
651651
*/
652652
@SuppressWarnings('PMD.AvoidDebugStatements')

nebula-logger/core/main/logger-engine/classes/Logger.cls

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
global with sharing class Logger {
1616
// There's no reliable way to get the version number dynamically in Apex
1717
@TestVisible
18-
private static final String CURRENT_VERSION_NUMBER = 'v4.14.13';
18+
private static final String CURRENT_VERSION_NUMBER = 'v4.14.14';
1919
private static final System.LoggingLevel FALLBACK_LOGGING_LEVEL = System.LoggingLevel.DEBUG;
2020
private static final List<LogEntryEventBuilder> LOG_ENTRIES_BUFFER = new List<LogEntryEventBuilder>();
2121
private static final String MISSING_SCENARIO_ERROR_MESSAGE = 'No logger scenario specified. A scenario is required for logging in this org.';
2222
private static final String ORGANIZATION_DOMAIN_URL = System.URL.getOrgDomainUrl()?.toExternalForm();
2323
private static final String REQUEST_ID = System.Request.getCurrent().getRequestId();
2424
private static final Map<String, SaveMethod> SAVE_METHOD_NAME_TO_SAVE_METHOD = new Map<String, SaveMethod>();
25+
private static final Map<Schema.SObjectField, Object> TRANSACTION_FIELD_TO_VALUE = new Map<Schema.SObjectField, Object>();
2526
private static final String TRANSACTION_ID = System.UUID.randomUUID().toString();
2627

2728
private static AsyncContext currentAsyncContext;
@@ -250,6 +251,30 @@ global with sharing class Logger {
250251
return parentLogTransactionId;
251252
}
252253

254+
/**
255+
* @description Sets a field value on every generated `LogEntryEvent__e` record
256+
* @param field The `Schema.SObjectField` token of the field to populate
257+
* on each `LogEntryEvent__e` record in the current transaction
258+
* @param fieldValue The `Object` value to populate in the provided field
259+
*/
260+
global static void setField(Schema.SObjectField field, Object fieldValue) {
261+
setField(new Map<Schema.SObjectField, Object>{ field => fieldValue });
262+
}
263+
264+
/**
265+
* @description Sets multiple field values oon every generated `LogEntryEvent__e` record
266+
* @param fieldToValue An instance of `Map<Schema.SObjectField, Object>` containing the
267+
* the fields & values to populate on each `LogEntryEvent__e` record in the current transaction
268+
*/
269+
global static void setField(Map<Schema.SObjectField, Object> fieldToValue) {
270+
if (getUserSettings().IsEnabled__c == false) {
271+
return;
272+
}
273+
274+
TRANSACTION_FIELD_TO_VALUE.putAll(fieldToValue);
275+
TRANSACTION_FIELD_TO_VALUE.remove(null);
276+
}
277+
253278
/**
254279
* @description Indicates if logging has been enabled for the current user, based on the custom setting LoggerSettings__c
255280
* @return Boolean
@@ -3390,8 +3415,9 @@ global with sharing class Logger {
33903415

33913416
return logEntryEventBuilder;
33923417
}
3393-
33943418
private static void finalizeEntry(LogEntryEvent__e logEntryEvent) {
3419+
setTransactionFields(logEntryEvent);
3420+
33953421
logEntryEvent.ParentLogTransactionId__c = getParentLogTransactionId();
33963422
logEntryEvent.TransactionScenario__c = transactionScenario;
33973423

@@ -3403,6 +3429,25 @@ global with sharing class Logger {
34033429
}
34043430
}
34053431

3432+
@SuppressWarnings('PMD.AvoidDebugStatements')
3433+
private static void setTransactionFields(LogEntryEvent__e logEntryEvent) {
3434+
for (Schema.SObjectField field : TRANSACTION_FIELD_TO_VALUE.keySet()) {
3435+
Object value = TRANSACTION_FIELD_TO_VALUE.get(field);
3436+
3437+
try {
3438+
Schema.DescribeFieldResult fieldDescribe = field.getDescribe();
3439+
if (fieldDescribe.getSoapType() == Schema.SoapType.STRING) {
3440+
value = LoggerDataStore.truncateFieldValue(field, (String) value);
3441+
}
3442+
3443+
logEntryEvent.put(field, value);
3444+
} catch (System.Exception ex) {
3445+
LogMessage logMessage = new LogMessage('Could not set field {0} with value {1}', field, value);
3446+
System.debug(System.LoggingLevel.WARN, logMessage.getMessage());
3447+
}
3448+
}
3449+
}
3450+
34063451
private static Boolean hasValidStartAndEndTimes(LoggerSettings__c settings) {
34073452
Datetime nowish = System.now();
34083453
Boolean isStartTimeValid = settings.StartTime__c == null || settings.StartTime__c <= nowish;

nebula-logger/core/main/logger-engine/lwc/logger/__tests__/logger.test.js

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,6 @@ describe('logger lwc recommended sync getLogger() import approach tests', () =>
543543
// getLogger() is built to be sync, but internally, some async tasks must execute
544544
// before some sync tasks are executed
545545
await flushPromises('Resolve async task queue');
546-
await logger.getUserSettings();
547546

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

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

558-
it('sets multiple custom fields', async () => {
557+
it('sets multiple custom component fields on subsequent entries', async () => {
558+
getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS });
559+
const logger = getLogger();
560+
// getLogger() is built to be sync, but internally, some async tasks must execute
561+
// before some sync tasks are executed
562+
await flushPromises('Resolve async task queue');
563+
const firstFakeFieldName = 'SomeField__c';
564+
const firstFieldMockValue = 'something';
565+
const secondFakeFieldName = 'AnotherField__c';
566+
const secondFieldMockValue = 'another value';
567+
568+
const previousLogEntry = logger.info('example log entry from before setField() is called').getComponentLogEntry();
569+
logger.setField({
570+
[firstFakeFieldName]: firstFieldMockValue,
571+
[secondFakeFieldName]: secondFieldMockValue
572+
});
573+
const subsequentLogEntry = logger.info('example log entry from after setField() is called').getComponentLogEntry();
574+
575+
expect(previousLogEntry.fieldToValue[firstFakeFieldName]).toBeUndefined();
576+
expect(previousLogEntry.fieldToValue[secondFakeFieldName]).toBeUndefined();
577+
expect(subsequentLogEntry.fieldToValue[firstFakeFieldName]).toEqual(firstFieldMockValue);
578+
expect(subsequentLogEntry.fieldToValue[secondFakeFieldName]).toEqual(secondFieldMockValue);
579+
});
580+
581+
it('sets multiple custom entry fields on a single entry', async () => {
559582
getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS });
560583
const logger = getLogger();
561584
// getLogger() is built to be sync, but internally, some async tasks must execute
562585
// before some sync tasks are executed
563586
await flushPromises('Resolve async task queue');
564-
await logger.getUserSettings();
565587
const logEntryBuilder = logger.info('example log entry');
566588
const logEntry = logEntryBuilder.getComponentLogEntry();
567589
const firstFakeFieldName = 'SomeField__c';
@@ -586,7 +608,6 @@ describe('logger lwc recommended sync getLogger() import approach tests', () =>
586608
// getLogger() is built to be sync, but internally, some async tasks must execute
587609
// before some sync tasks are executed
588610
await flushPromises('Resolve async task queue');
589-
await logger.getUserSettings();
590611
const logEntryBuilder = logger.info('example log entry');
591612
const logEntry = logEntryBuilder.getComponentLogEntry();
592613
expect(logEntry.recordId).toBeFalsy();
@@ -603,7 +624,6 @@ describe('logger lwc recommended sync getLogger() import approach tests', () =>
603624
// getLogger() is built to be sync, but internally, some async tasks must execute
604625
// before some sync tasks are executed
605626
await flushPromises('Resolve async task queue');
606-
await logger.getUserSettings();
607627
const logEntryBuilder = logger.info('example log entry');
608628
const logEntry = logEntryBuilder.getComponentLogEntry();
609629
expect(logEntry.record).toBeFalsy();
@@ -620,7 +640,6 @@ describe('logger lwc recommended sync getLogger() import approach tests', () =>
620640
// getLogger() is built to be sync, but internally, some async tasks must execute
621641
// before some sync tasks are executed
622642
await flushPromises('Resolve async task queue');
623-
await logger.getUserSettings();
624643
const logEntryBuilder = logger.info('example log entry');
625644
const logEntry = logEntryBuilder.getComponentLogEntry();
626645
expect(logEntry.error).toBeFalsy();
@@ -1147,7 +1166,29 @@ describe('logger lwc deprecated async createLogger() import tests', () => {
11471166
expect(logEntry.browser.windowResolution).toEqual(window.innerWidth + ' x ' + window.innerHeight);
11481167
});
11491168

1150-
it('sets multiple custom fields when using deprecated async createLogger() import approach', async () => {
1169+
it('sets multiple custom component fields on subsequent entries when using deprecated async createLogger() import approach', async () => {
1170+
getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS });
1171+
const logger = await createLogger();
1172+
await logger.getUserSettings();
1173+
const firstFakeFieldName = 'SomeField__c';
1174+
const firstFieldMockValue = 'something';
1175+
const secondFakeFieldName = 'AnotherField__c';
1176+
const secondFieldMockValue = 'another value';
1177+
1178+
const previousLogEntry = logger.info('example log entry from before setField() is called').getComponentLogEntry();
1179+
logger.setField({
1180+
[firstFakeFieldName]: firstFieldMockValue,
1181+
[secondFakeFieldName]: secondFieldMockValue
1182+
});
1183+
const subsequentLogEntry = logger.info('example log entry from after setField() is called').getComponentLogEntry();
1184+
1185+
expect(previousLogEntry.fieldToValue[firstFakeFieldName]).toBeUndefined();
1186+
expect(previousLogEntry.fieldToValue[secondFakeFieldName]).toBeUndefined();
1187+
expect(subsequentLogEntry.fieldToValue[firstFakeFieldName]).toEqual(firstFieldMockValue);
1188+
expect(subsequentLogEntry.fieldToValue[secondFakeFieldName]).toEqual(secondFieldMockValue);
1189+
});
1190+
1191+
it('sets multiple custom entry fields on a single entry when using deprecated async createLogger() import approach', async () => {
11511192
getSettings.mockResolvedValue({ ...MOCK_GET_SETTINGS });
11521193
const logger = await createLogger();
11531194
await logger.getUserSettings();

nebula-logger/core/main/logger-engine/lwc/logger/logger.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ export default class Logger extends LightningElement {
2222
return this.#loggerService.getUserSettings();
2323
}
2424

25+
/**
26+
* @description Sets multiple field values on the builder's `LogEntryEvent__e` record
27+
* @param {Object} fieldToValue An object containing the custom field name as a key, with the corresponding value to store.
28+
* Example: `{"SomeField__c": "some value", "AnotherField__c": "another value"}`
29+
*/
30+
setField(fieldToValue) {
31+
this.#loggerService.setField(fieldToValue);
32+
}
33+
2534
/**
2635
* @description Sets the scenario name for the current transaction - this is stored in `LogEntryEvent__e.Scenario__c`
2736
* and `Log__c.Scenario__c`, and can be used to filter & group logs

0 commit comments

Comments
 (0)