Skip to content

Commit

Permalink
Added a new Apex static method Logger.setField() so custom fields can…
Browse files Browse the repository at this point in the history
… be set once per transaction --> auto-populated on all LogEntryEvent__e records
  • Loading branch information
jongpie committed Oct 11, 2024
1 parent 2445dcb commit 576fdf2
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 8 deletions.
2 changes: 1 addition & 1 deletion docs/apex/Logger-Engine/LogEntryEventBuilder.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
21 changes: 21 additions & 0 deletions docs/apex/Logger-Engine/Logger.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Schema.SObjectField, Object> fieldToValue)``void`

Sets multiple field values oon every generated `LogEntryEvent__e` record

##### Parameters

| Param | Description |
| -------------- | ---------------------------------------------------------------------- |
| `fieldToValue` | An instance of `Map&lt;Schema.SObjectField, Object&gt;` containing the |

#### `setParentLogTransactionId(String parentTransactionId)``void`

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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<Schema.SObjectField, Object>` 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')
Expand Down
47 changes: 46 additions & 1 deletion nebula-logger/core/main/logger-engine/classes/Logger.cls
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, SaveMethod> SAVE_METHOD_NAME_TO_SAVE_METHOD = new Map<String, SaveMethod>();
private static final Map<Schema.SObjectField, Object> TRANSACTION_FIELD_TO_VALUE = new Map<Schema.SObjectField, Object>();
private static final String TRANSACTION_ID = System.UUID.randomUUID().toString();

private static AsyncContext currentAsyncContext;
Expand Down Expand Up @@ -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<Schema.SObjectField, Object>{ field => fieldValue });
}

/**
* @description Sets multiple field values oon every generated `LogEntryEvent__e` record
* @param fieldToValue An instance of `Map<Schema.SObjectField, Object>` containing the
* the fields & values to populate on each `LogEntryEvent__e` record in the current transaction
*/
global static void setField(Map<Schema.SObjectField, Object> 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
Expand Down Expand Up @@ -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;

Expand All @@ -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;
Expand Down
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.13';
const CURRENT_VERSION_NUMBER = 'v4.14.14';

const CONSOLE_OUTPUT_CONFIG = {
messagePrefix: `%c Nebula Logger ${CURRENT_VERSION_NUMBER} `,
Expand Down
60 changes: 60 additions & 0 deletions nebula-logger/core/tests/logger-engine/classes/Logger_Tests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -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<LogEntryEvent__e>) 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<Schema.SObjectField, Object>{ field => fieldValue });
}
}
Logger.saveLog();

System.Assert.areEqual(countOfEntriesToAdd, LoggerMockDataStore.getEventBus().getPublishedPlatformEvents().size());
for (LogEntryEvent__e publishedLogEntryEvent : (List<LogEntryEvent__e>) LoggerMockDataStore.getEventBus().getPublishedPlatformEvents()) {
System.Assert.areEqual(fieldValue, publishedLogEntryEvent.get(field));
}
}
// End setField() test methods

@IsTest
static void it_should_return_quiddity_level() {
List<System.Quiddity> acceptableDefaultQuidditiesForTests = new List<System.Quiddity>{
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.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"
Expand Down

0 comments on commit 576fdf2

Please sign in to comment.