diff --git a/docs/apex/Logger-Engine/LogEntryEventBuilder.md b/docs/apex/Logger-Engine/LogEntryEventBuilder.md index 30c2448f7..da9ee226c 100644 --- a/docs/apex/Logger-Engine/LogEntryEventBuilder.md +++ b/docs/apex/Logger-Engine/LogEntryEventBuilder.md @@ -402,7 +402,7 @@ The same instance of `LogEntryEventBuilder`, useful for chaining methods #### `setField(Schema.SObjectField field, Object fieldValue)` → `LogEntryEventBuilder` -Sets a field values on the builder's `LogEntryEvent__e` record +Sets a field value on the builder's `LogEntryEvent__e` record ##### Parameters diff --git a/docs/apex/Logger-Engine/Logger.md b/docs/apex/Logger-Engine/Logger.md index 92b44730d..0c2785cb6 100644 --- a/docs/apex/Logger-Engine/Logger.md +++ b/docs/apex/Logger-Engine/Logger.md @@ -4948,6 +4948,27 @@ Stores additional details about the current transacation's async context | -------------------- | ------------------------------------------------------ | | `schedulableContext` | - The instance of `System.SchedulableContext` to track | +#### `setField(Schema.SObjectField field, Object fieldValue)` → `void` + +Sets a field value on every generated `LogEntryEvent__e` record + +##### Parameters + +| Param | Description | +| ------------ | -------------------------------------------------------- | +| `field` | The `Schema.SObjectField` token of the field to populate | +| `fieldValue` | The `Object` value to populate in the provided field | + +#### `setField(Map fieldToValue)` → `void` + +Sets multiple field values oon every generated `LogEntryEvent__e` record + +##### Parameters + +| Param | Description | +| -------------- | ---------------------------------------------------------------------- | +| `fieldToValue` | An instance of `Map<Schema.SObjectField, Object>` containing the | + #### `setParentLogTransactionId(String parentTransactionId)` → `void` Relates the current transaction'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 & queueable jobs. diff --git a/nebula-logger/core/main/logger-engine/classes/LogEntryEventBuilder.cls b/nebula-logger/core/main/logger-engine/classes/LogEntryEventBuilder.cls index 6d8938bf2..d93a099c3 100644 --- a/nebula-logger/core/main/logger-engine/classes/LogEntryEventBuilder.cls +++ b/nebula-logger/core/main/logger-engine/classes/LogEntryEventBuilder.cls @@ -629,7 +629,7 @@ global with sharing class LogEntryEventBuilder { } /** - * @description Sets a field values on the builder's `LogEntryEvent__e` record + * @description Sets a field value on the builder's `LogEntryEvent__e` record * @param field The `Schema.SObjectField` token of the field to populate * on the builder's `LogEntryEvent__e` record * @param fieldValue The `Object` value to populate in the provided field @@ -646,7 +646,7 @@ global with sharing class LogEntryEventBuilder { /** * @description Sets multiple field values on the builder's `LogEntryEvent__e` record * @param fieldToValue An instance of `Map` containing the - * the fields & values to populate the builder's `LogEntryEvent__e` record + * the fields & values to populate on the builder's `LogEntryEvent__e` record * @return The same instance of `LogEntryEventBuilder`, useful for chaining methods */ @SuppressWarnings('PMD.AvoidDebugStatements') diff --git a/nebula-logger/core/main/logger-engine/classes/Logger.cls b/nebula-logger/core/main/logger-engine/classes/Logger.cls index a95882434..5a0256b33 100644 --- a/nebula-logger/core/main/logger-engine/classes/Logger.cls +++ b/nebula-logger/core/main/logger-engine/classes/Logger.cls @@ -22,6 +22,7 @@ global with sharing class Logger { private static final String ORGANIZATION_DOMAIN_URL = System.URL.getOrgDomainUrl()?.toExternalForm(); private static final String REQUEST_ID = System.Request.getCurrent().getRequestId(); private static final Map SAVE_METHOD_NAME_TO_SAVE_METHOD = new Map(); + private static final Map TRANSACTION_FIELD_TO_VALUE = new Map(); private static final String TRANSACTION_ID = System.UUID.randomUUID().toString(); private static AsyncContext currentAsyncContext; @@ -250,6 +251,30 @@ global with sharing class Logger { return parentLogTransactionId; } + /** + * @description Sets a field value on every generated `LogEntryEvent__e` record + * @param field The `Schema.SObjectField` token of the field to populate + * on each `LogEntryEvent__e` record in the current transaction + * @param fieldValue The `Object` value to populate in the provided field + */ + global static void setField(Schema.SObjectField field, Object fieldValue) { + setField(new Map{ field => fieldValue }); + } + + /** + * @description Sets multiple field values oon every generated `LogEntryEvent__e` record + * @param fieldToValue An instance of `Map` containing the + * the fields & values to populate on each `LogEntryEvent__e` record in the current transaction + */ + global static void setField(Map fieldToValue) { + if (getUserSettings().IsEnabled__c == false) { + return; + } + + TRANSACTION_FIELD_TO_VALUE.putAll(fieldToValue); + TRANSACTION_FIELD_TO_VALUE.remove(null); + } + /** * @description Indicates if logging has been enabled for the current user, based on the custom setting LoggerSettings__c * @return Boolean @@ -3390,8 +3415,9 @@ global with sharing class Logger { return logEntryEventBuilder; } - private static void finalizeEntry(LogEntryEvent__e logEntryEvent) { + setTransactionFields(logEntryEvent); + logEntryEvent.ParentLogTransactionId__c = getParentLogTransactionId(); logEntryEvent.TransactionScenario__c = transactionScenario; @@ -3403,6 +3429,25 @@ global with sharing class Logger { } } + @SuppressWarnings('PMD.AvoidDebugStatements') + private static void setTransactionFields(LogEntryEvent__e logEntryEvent) { + for (Schema.SObjectField field : TRANSACTION_FIELD_TO_VALUE.keySet()) { + Object value = TRANSACTION_FIELD_TO_VALUE.get(field); + + try { + Schema.DescribeFieldResult fieldDescribe = field.getDescribe(); + if (fieldDescribe.getSoapType() == Schema.SoapType.STRING) { + value = LoggerDataStore.truncateFieldValue(field, (String) value); + } + + logEntryEvent.put(field, value); + } catch (System.Exception ex) { + LogMessage logMessage = new LogMessage('Could not set field {0} with value {1}', field, value); + System.debug(System.LoggingLevel.WARN, logMessage.getMessage()); + } + } + } + private static Boolean hasValidStartAndEndTimes(LoggerSettings__c settings) { Datetime nowish = System.now(); Boolean isStartTimeValid = settings.StartTime__c == null || settings.StartTime__c <= nowish; 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 5ccfa3f66..e5cbc0961 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.13'; +const CURRENT_VERSION_NUMBER = 'v4.14.14'; const CONSOLE_OUTPUT_CONFIG = { messagePrefix: `%c Nebula Logger ${CURRENT_VERSION_NUMBER} `, diff --git a/nebula-logger/core/tests/logger-engine/classes/Logger_Tests.cls b/nebula-logger/core/tests/logger-engine/classes/Logger_Tests.cls index 5ea0c6a26..2bd16ad8b 100644 --- a/nebula-logger/core/tests/logger-engine/classes/Logger_Tests.cls +++ b/nebula-logger/core/tests/logger-engine/classes/Logger_Tests.cls @@ -1019,6 +1019,66 @@ private class Logger_Tests { System.Assert.isNull(Logger.getParentLogTransactionId()); } + // Start setField() test methods + @IsTest + static void it_should_set_single_transaction_field_on_all_entries_when_events_published() { + LoggerDataStore.setMock(LoggerMockDataStore.getEventBus()); + System.Assert.areEqual(0, LoggerMockDataStore.getEventBus().getPublishedPlatformEvents().size()); + // HttpRequestBody__c is used as an example here since it's provided out of the box, and doesn't + // have a default value set. + // But realisticially, this functionality is intended to be used with custom fields that are added to + // Nebula Logger's data model. + Schema.SObjectField field = Schema.LogEntryEvent__e.HttpRequestBody__c; + String fieldValue = 'Some_value'; + Integer countOfEntriesToAdd = 3; + + for (Integer i = 0; i < countOfEntriesToAdd; i++) { + LogEntryEventBuilder builder = Logger.info('hello, world'); + System.Assert.isNull(builder.getLogEntryEvent().get(field), 'Field ' + field + ' should be null until the event is published'); + // Call setField() after some entries have been added (and before other entries are added) + // to ensure that all entries have the fields populated on publish + if (i == 1) { + Logger.setField(field, fieldValue); + } + } + Logger.saveLog(); + + System.Assert.areEqual(countOfEntriesToAdd, LoggerMockDataStore.getEventBus().getPublishedPlatformEvents().size()); + for (LogEntryEvent__e publishedLogEntryEvent : (List) LoggerMockDataStore.getEventBus().getPublishedPlatformEvents()) { + System.Assert.areEqual(fieldValue, publishedLogEntryEvent.get(field)); + } + } + + @IsTest + static void it_should_set_map_of_transaction_fields_on_all_entries_when_events_published() { + LoggerDataStore.setMock(LoggerMockDataStore.getEventBus()); + System.Assert.areEqual(0, LoggerMockDataStore.getEventBus().getPublishedPlatformEvents().size()); + // HttpRequestBody__c is used as an example here since it's provided out of the box, and doesn't + // have a default value set. + // But realisticially, this functionality is intended to be used with custom fields that are added to + // Nebula Logger's data model. + Schema.SObjectField field = Schema.LogEntryEvent__e.HttpRequestBody__c; + String fieldValue = 'Some_value'; + Integer countOfEntriesToAdd = 3; + + for (Integer i = 0; i < countOfEntriesToAdd; i++) { + LogEntryEventBuilder builder = Logger.info('hello, world'); + System.Assert.isNull(builder.getLogEntryEvent().get(field), 'Field ' + field + ' should be null until the event is published'); + // Call setField() after some entries have been added (and before other entries are added) + // to ensure that all entries have the fields populated on publish + if (i == 1) { + Logger.setField(new Map{ field => fieldValue }); + } + } + Logger.saveLog(); + + System.Assert.areEqual(countOfEntriesToAdd, LoggerMockDataStore.getEventBus().getPublishedPlatformEvents().size()); + for (LogEntryEvent__e publishedLogEntryEvent : (List) LoggerMockDataStore.getEventBus().getPublishedPlatformEvents()) { + System.Assert.areEqual(fieldValue, publishedLogEntryEvent.get(field)); + } + } + // End setField() test methods + @IsTest static void it_should_return_quiddity_level() { List acceptableDefaultQuidditiesForTests = new List{ diff --git a/sfdx-project.json b/sfdx-project.json index d2f8fa772..bda065d4f 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.13.NEXT", - "versionName": "New getLogger() JS Function", - "versionDescription": "Added a new function getLogger() in c/logger that can be called synchronously (createLogger() was async). This simplifies how developers use it, and avoids some lingering JS stack trace issues that occur in async functions.", + "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", "releaseNotesUrl": "https://github.com/jongpie/NebulaLogger/releases", "unpackagedMetadata": { "path": "./nebula-logger/extra-tests"