From 6453b1726894f14f717fd5faf134eb090937d856 Mon Sep 17 00:00:00 2001
From: James Simone <16430727+jamessimone@users.noreply.github.com>
Date: Fri, 29 Mar 2024 12:02:36 -0400
Subject: [PATCH] Logger Performance Improvements (#660)
* Benchmarking & performance improvements for transactions with many log entries
* Removes duplicated field truncation from LoggerDataStore's pre-platform event publish responsibilities
* Further optimizations - strip out Logging classes prior to entering LoggerStackTrace, saving an average of 3 seconds processing time over 500 log entries
* Adds PMD7 suppressions where applicable
* Standardizing how timestamps are added to and maintained within LogEntryEventBuilder with feedback from @jongpie
* Code review feedback from @jongpie - changed LoggerParameter.StoreApexHeapSizeLimit to LoggerParameter.StoreHeapSizeLimit, standardized referring to the limit in the singular as opposed to plural, and added two more tests: 1) LogEntryEventBuilder_Tests.it_should_not_set_transaction_heap_limits_fields_when_transaction_limit_tracking_is_disabled_via_logger_parameter(), and 2) LoggerBenchmarking_Tests.it_benchmarks_without_setting_heap_limit()
---
README.md | 10 +-
.../configuration/classes/LoggerCache.cls | 2 +-
.../configuration/classes/LoggerParameter.cls | 17 +-
...erParameter.StoreHeapSizeLimit.md-meta.xml | 25 +
.../log-management/classes/LogBatchPurger.cls | 2 +-
.../classes/LoggerEmailSender.cls | 2 +-
.../classes/LoggerSettingsController.cls | 2 +-
.../logger-engine/classes/ComponentLogger.cls | 20 +-
.../main/logger-engine/classes/FlowLogger.cls | 2 +-
.../classes/LogEntryEventBuilder.cls | 432 ++++++++----------
.../main/logger-engine/classes/Logger.cls | 87 ++--
.../logger-engine/classes/LoggerDataStore.cls | 94 +---
.../classes/LoggerStackTrace.cls | 13 -
.../lwc/logger/logEntryBuilder.js | 2 +-
.../utilities/LoggerMockDataCreator.cls | 2 +-
.../classes/LogEntryEventBuilder_Tests.cls | 217 ++++-----
.../classes/LoggerDataStore_Tests.cls | 63 ---
.../logger-engine/classes/Logger_Tests.cls | 42 +-
.../utilities/LoggerMockDataStore.cls | 13 +-
.../LogEntryEventBuilder_Tests_Network.cls | 4 +-
.../LogEntryEventBuilder_Tests_Security.cls | 6 +-
.../tests/LoggerBenchmarking_Tests.cls | 55 +++
.../LoggerBenchmarking_Tests.cls-meta.xml | 5 +
.../tests/Logger_Tests_MergeResult.cls | 2 +-
.../plugin/classes/LogRetentionFilter.cls | 1 +
package.json | 2 +-
scripts/data/create-sample-log-entries.apex | 3 +-
sfdx-project.json | 7 +-
28 files changed, 512 insertions(+), 620 deletions(-)
create mode 100644 nebula-logger/core/main/configuration/customMetadata/LoggerParameter.StoreHeapSizeLimit.md-meta.xml
create mode 100644 nebula-logger/extra-tests/tests/LoggerBenchmarking_Tests.cls
create mode 100644 nebula-logger/extra-tests/tests/LoggerBenchmarking_Tests.cls-meta.xml
diff --git a/README.md b/README.md
index 1e7e7addc..34b88daac 100644
--- a/README.md
+++ b/README.md
@@ -5,15 +5,15 @@
The most robust logger for Salesforce. Works with Apex, Lightning Components, Flow, Process Builder & Integrations. Designed for Salesforce admins, developers & architects.
-## Unlocked Package - v4.13.4
+## Unlocked Package - v4.13.5
-[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y000001MkFBQA0)
-[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y000001MkFBQA0)
+[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y000001MkGnQAK)
+[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y000001MkGnQAK)
[![View Documentation](./images/btn-view-documentation.png)](https://jongpie.github.io/NebulaLogger/)
-`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y000001MkFBQA0`
+`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y000001MkGnQAK`
-`sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y000001MkFBQA0`
+`sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y000001MkGnQAK`
---
diff --git a/nebula-logger/core/main/configuration/classes/LoggerCache.cls b/nebula-logger/core/main/configuration/classes/LoggerCache.cls
index 036a39226..4380f6d3c 100644
--- a/nebula-logger/core/main/configuration/classes/LoggerCache.cls
+++ b/nebula-logger/core/main/configuration/classes/LoggerCache.cls
@@ -7,7 +7,7 @@
* @group Configuration
* @description Class used to cache query results returned by the selector classes
*/
-@SuppressWarnings('PMD.ExcessivePublicCount')
+@SuppressWarnings('PMD.CognitiveComplexity, PMD.ExcessivePublicCount')
public without sharing class LoggerCache {
private static final String DEFAULT_PARTITION_NAME = 'LoggerCache';
private static final Boolean PLATFORM_CACHE_IS_IMMUTABLE = false;
diff --git a/nebula-logger/core/main/configuration/classes/LoggerParameter.cls b/nebula-logger/core/main/configuration/classes/LoggerParameter.cls
index 80831de63..cc1fcb443 100644
--- a/nebula-logger/core/main/configuration/classes/LoggerParameter.cls
+++ b/nebula-logger/core/main/configuration/classes/LoggerParameter.cls
@@ -8,7 +8,7 @@
* @description Provides a centralized way to load parameters for SObject handlers & plugins,
* and casts the parameters to common data types
*/
-@SuppressWarnings('PMD.CyclomaticComplexity, PMD.ExcessivePublicCount, PMD.PropertyNamingConventions')
+@SuppressWarnings('PMD.CognitiveComplexity, PMD.CyclomaticComplexity, PMD.ExcessivePublicCount, PMD.PropertyNamingConventions')
public class LoggerParameter {
// During tests, always load this CMDT record
// so that tests use the same format when calling System.debug()
@@ -369,6 +369,21 @@ public class LoggerParameter {
private set;
}
+ /**
+ * @description Indicates if Nebula Logger will store the transaction heap limits on `LogEntry__c`, retrieved from the class `System.Limits`.
+ * Controlled by the custom metadata record `LoggerParameter.StoreApexHeapSizeLimit`, or `true` as the default.
+ * Relies on `LoggerParameter.StoreTransactionLimits` to be true, as well.
+ */
+ public static final Boolean STORE_HEAP_SIZE_LIMIT {
+ get {
+ if (STORE_HEAP_SIZE_LIMIT == null) {
+ STORE_HEAP_SIZE_LIMIT = getBoolean('StoreHeapSizeLimit', true);
+ }
+ return STORE_HEAP_SIZE_LIMIT;
+ }
+ private set;
+ }
+
/**
* @description Indicates if Nebula Logger will store the header values when logging an instance of `System.HttpResponse`.
* Controlled by the custom metadata record `LoggerParameter.StoreHttpResponseHeaderValues`, or `true` as the default.
diff --git a/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.StoreHeapSizeLimit.md-meta.xml b/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.StoreHeapSizeLimit.md-meta.xml
new file mode 100644
index 000000000..4c15fc403
--- /dev/null
+++ b/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.StoreHeapSizeLimit.md-meta.xml
@@ -0,0 +1,25 @@
+
+
+
+ false
+
+ Comments__c
+
+
+
+ Description__c
+ When set to 'true' (default), transaction heap limits are retrieved from the class System.Limits and stored on LogEntry__c.
+
+When set to 'false', transaction heap limits are not retrieved or stored. This drastically helps reduce CPU time usage per log entry. The 'Store Transaction Limits' Logger Parameter record must also be set to 'true' for any limit tracking to occur.
+
+
+ Value__c
+ true
+
+
diff --git a/nebula-logger/core/main/log-management/classes/LogBatchPurger.cls b/nebula-logger/core/main/log-management/classes/LogBatchPurger.cls
index 0a29067e4..72aff2888 100644
--- a/nebula-logger/core/main/log-management/classes/LogBatchPurger.cls
+++ b/nebula-logger/core/main/log-management/classes/LogBatchPurger.cls
@@ -8,7 +8,7 @@
* @description Batch class used to delete old logs, based on `Log__c.LogRetentionDate__c <= :System.today()`
* @see LogBatchPurgeScheduler
*/
-@SuppressWarnings('PMD.AvoidGlobalModifier, PMD.CyclomaticComplexity, PMD.ExcessiveParameterList')
+@SuppressWarnings('PMD.AvoidGlobalModifier, PMD.CognitiveComplexity, PMD.CyclomaticComplexity, PMD.ExcessiveParameterList')
global with sharing class LogBatchPurger implements Database.Batchable, Database.Stateful {
private static final Integer DEFAULT_BATCH_SIZE = 2000;
private static final Date LOG_RETENTION_END_DATE = System.today();
diff --git a/nebula-logger/core/main/log-management/classes/LoggerEmailSender.cls b/nebula-logger/core/main/log-management/classes/LoggerEmailSender.cls
index 29cab5c51..0221d4c3d 100644
--- a/nebula-logger/core/main/log-management/classes/LoggerEmailSender.cls
+++ b/nebula-logger/core/main/log-management/classes/LoggerEmailSender.cls
@@ -7,7 +7,7 @@
* @group Log Management
* @description Builds and sends email notifications when internal exceptions occur within the logging system
*/
-@SuppressWarnings('PMD.PropertyNamingConventions')
+@SuppressWarnings('PMD.CognitiveComplexity, PMD.PropertyNamingConventions')
public without sharing class LoggerEmailSender {
@TestVisible
private static final List MOCK_NOTIFICATIONS = new List();
diff --git a/nebula-logger/core/main/log-management/classes/LoggerSettingsController.cls b/nebula-logger/core/main/log-management/classes/LoggerSettingsController.cls
index c77903c47..2e7573ebf 100644
--- a/nebula-logger/core/main/log-management/classes/LoggerSettingsController.cls
+++ b/nebula-logger/core/main/log-management/classes/LoggerSettingsController.cls
@@ -7,7 +7,7 @@
* @group Log Management
* @description Controller class for lwc `loggerSettings`, used to manage records in `LoggerSettings__c`
*/
-@SuppressWarnings('PMD.CyclomaticComplexity, PMD.ExcessivePublicCount')
+@SuppressWarnings('PMD.CognitiveComplexity, PMD.CyclomaticComplexity, PMD.ExcessivePublicCount')
public without sharing class LoggerSettingsController {
@TestVisible
private static final String CUSTOM_SAVE_METHOD_PREFIX = 'CustomSaveMethod';
diff --git a/nebula-logger/core/main/logger-engine/classes/ComponentLogger.cls b/nebula-logger/core/main/logger-engine/classes/ComponentLogger.cls
index 7e42bc816..385be2fca 100644
--- a/nebula-logger/core/main/logger-engine/classes/ComponentLogger.cls
+++ b/nebula-logger/core/main/logger-engine/classes/ComponentLogger.cls
@@ -61,7 +61,7 @@ public inherited sharing class ComponentLogger {
logEntryEventBuilder.setRecord(componentLogEntry.record).getLogEntryEvent();
}
- logEntryEvent.Timestamp__c = componentLogEntry.timestamp;
+ logEntryEventBuilder.setTimestamp(componentLogEntry.timestamp);
setBrowserDetails(logEntryEvent, componentLogEntry);
setComponentErrorDetails(logEntryEvent, componentLogEntry.error);
setOriginStackTraceDetails(logEntryEvent, componentLogEntry.stack);
@@ -106,7 +106,10 @@ public inherited sharing class ComponentLogger {
logEntryEvent.ExceptionSourceActionName__c = exceptionStackTrace.Source?.ActionName;
logEntryEvent.ExceptionSourceApiName__c = exceptionStackTrace.Source?.ApiName;
logEntryEvent.ExceptionSourceMetadataType__c = exceptionStackTrace.Source?.MetadataType.name();
- logEntryEvent.ExceptionStackTrace__c = exceptionStackTrace.ParsedStackTraceString;
+ logEntryEvent.ExceptionStackTrace__c = LoggerDataStore.truncateFieldValue(
+ Schema.LogEntryEvent__e.ExceptionStackTrace__c,
+ exceptionStackTrace.ParsedStackTraceString
+ );
}
@SuppressWarnings('PMD.AvoidDeeplyNestedIfStmts, PMD.CyclomaticComplexity, PMD.CognitiveComplexity, PMD.NcssMethodCount, PMD.StdCyclomaticComplexity')
@@ -127,7 +130,7 @@ public inherited sharing class ComponentLogger {
// New Origin fields
logEntryEvent.OriginLocation__c = originStackTrace.Location;
- logEntryEvent.StackTrace__c = stackTraceString;
+ logEntryEvent.StackTrace__c = LoggerDataStore.truncateFieldValue(Schema.LogEntryEvent__e.StackTrace__c, stackTraceString);
if (originStackTrace.Source != null) {
logEntryEvent.OriginSourceActionName__c = originStackTrace.Source.ActionName;
logEntryEvent.OriginSourceMetadataType__c = originStackTrace.Source.MetadataType.name();
@@ -135,17 +138,6 @@ public inherited sharing class ComponentLogger {
}
}
- private static String truncateFieldValue(Schema.SObjectField field, String value) {
- Integer fieldMaxLength = field.getDescribe().getLength();
- if (String.isBlank(value)) {
- return value;
- } else if (value.length() <= fieldMaxLength) {
- return value;
- }
-
- return value.left(fieldMaxLength);
- }
-
/**
* @description A DTO object used for passing `LoggerSettings__c` details to lightning components
*/
diff --git a/nebula-logger/core/main/logger-engine/classes/FlowLogger.cls b/nebula-logger/core/main/logger-engine/classes/FlowLogger.cls
index 2ba15a83c..34ae01df8 100644
--- a/nebula-logger/core/main/logger-engine/classes/FlowLogger.cls
+++ b/nebula-logger/core/main/logger-engine/classes/FlowLogger.cls
@@ -132,7 +132,7 @@ public inherited sharing class FlowLogger {
this.logEntryEvent.OriginSourceApiName__c = this.flowName;
this.logEntryEvent.OriginSourceMetadataType__c = FLOW_SOURCE_METADATA_TYPE;
this.logEntryEvent.OriginType__c = FLOW_SOURCE_METADATA_TYPE;
- this.logEntryEvent.Timestamp__c = this.timestamp;
+ this.logEntryEventBuilder.setTimestamp(this.timestamp);
// Flow does not have an equivalent to a stack trace, so always clear the fields
this.logEntryEvent.ExceptionStackTrace__c = null;
this.logEntryEvent.StackTrace__c = null;
diff --git a/nebula-logger/core/main/logger-engine/classes/LogEntryEventBuilder.cls b/nebula-logger/core/main/logger-engine/classes/LogEntryEventBuilder.cls
index 128964bd6..d050c3cae 100644
--- a/nebula-logger/core/main/logger-engine/classes/LogEntryEventBuilder.cls
+++ b/nebula-logger/core/main/logger-engine/classes/LogEntryEventBuilder.cls
@@ -9,7 +9,7 @@
* @see Logger
*/
@SuppressWarnings(
- 'PMD.AvoidGlobalModifier, PMD.CognitiveComplexity, PMD.CyclomaticComplexity, PMD.ExcessiveClassLength, PMD.NcssTypeCount, PMD.PropertyNamingConventions, PMD.StdCyclomaticComplexity'
+ 'PMD.AvoidGlobalModifier, PMD.CognitiveComplexity, PMD.CyclomaticComplexity, PMD.ExcessiveClassLength, PMD.ExcessivePublicCount, PMD.NcssTypeCount, PMD.PropertyNamingConventions, PMD.StdCyclomaticComplexity'
)
global with sharing class LogEntryEventBuilder {
private static final Map CACHED_SOBJECT_NAME_TO_CLASSIFICATION = new Map();
@@ -74,22 +74,22 @@ global with sharing class LogEntryEventBuilder {
set;
}
- private static final Schema.User CACHED_USER {
+ private static final TransactionLimits CACHED_TRANSACTION_LIMITS {
get {
- if (CACHED_USER == null) {
- CACHED_USER = LoggerEngineDataSelector.getInstance().getCachedUser();
+ if (CACHED_TRANSACTION_LIMITS == null) {
+ CACHED_TRANSACTION_LIMITS = new TransactionLimits();
}
- return CACHED_USER;
+ return CACHED_TRANSACTION_LIMITS;
}
set;
}
- private static final LogEntryEvent__e LOG_ENTRY_EVENT_TEMPLATE {
+ private static final Schema.User CACHED_USER {
get {
- if (LOG_ENTRY_EVENT_TEMPLATE == null) {
- LOG_ENTRY_EVENT_TEMPLATE = getLogEntryEventTemplate();
+ if (CACHED_USER == null) {
+ CACHED_USER = LoggerEngineDataSelector.getInstance().getCachedUser();
}
- return LOG_ENTRY_EVENT_TEMPLATE.clone();
+ return CACHED_USER;
}
set;
}
@@ -114,19 +114,6 @@ global with sharing class LogEntryEventBuilder {
set;
}
- /**
- * @description `Deprecated` - Formally used by `Logger` to instantiate a new instance of `LogEntryEventBuilder`
- * @param userSettings The instance of `LoggerSettings__c` for the current to use to control any feature flags
- * @param entryLoggingLevel The `LoggingLevel` value to use for the log entry
- * @param shouldSave Indicates if the builder's instance of `LogEntryEvent__e` should be saved
- * @param ignoredOrigins A `Set` of the names of any Apex classes that should be ignored when parsing the entry's origin
- */
- @SuppressWarnings('PMD.ExcessiveParameterList, PMD.NcssConstructorCount')
- public LogEntryEventBuilder(LoggerSettings__c userSettings, System.LoggingLevel entryLoggingLevel, Boolean shouldSave, Set ignoredOrigins) {
- // TOODO eliminate this constructor
- this(userSettings, entryLoggingLevel, shouldSave);
- }
-
/**
* @description Used by `Logger` to instantiate a new instance of `LogEntryEventBuilder`
* @param userSettings The instance of `LoggerSettings__c` for the current to use to control any feature flags
@@ -136,45 +123,13 @@ global with sharing class LogEntryEventBuilder {
@SuppressWarnings('PMD.NcssConstructorCount')
public LogEntryEventBuilder(LoggerSettings__c userSettings, System.LoggingLevel entryLoggingLevel, Boolean shouldSave) {
this.shouldSave = shouldSave;
- if (this.shouldSave() == false) {
- return;
- }
+ if (this.shouldSave()) {
+ this.userSettings = userSettings;
+ this.entryLoggingLevel = entryLoggingLevel;
- this.userSettings = userSettings;
- this.entryLoggingLevel = entryLoggingLevel;
-
- Datetime timestamp = System.now();
- this.logEntryEvent = new LogEntryEvent__e(
- EpochTimestamp__c = timestamp.getTime(),
- LoggingLevel__c = entryLoggingLevel.name(),
- LoggingLevelOrdinal__c = entryLoggingLevel.ordinal(),
- OriginType__c = 'Apex',
- Timestamp__c = timestamp,
- TriggerIsExecuting__c = Trigger.isExecuting,
- TriggerOperationType__c = Trigger.operationType?.name(),
- TriggerSObjectType__c = Trigger.new?.getSObjectType().getDescribe().getName()
- );
-
- if (LoggerParameter.STORE_TRANSACTION_LIMITS) {
- this.logEntryEvent.LimitsAggregateQueriesUsed__c = System.Limits.getAggregateQueries();
- this.logEntryEvent.LimitsAsyncCallsUsed__c = System.Limits.getAsyncCalls();
- this.logEntryEvent.LimitsCalloutsUsed__c = System.Limits.getCallouts();
- this.logEntryEvent.LimitsCpuTimeUsed__c = System.Limits.getCpuTime();
- this.logEntryEvent.LimitsDmlRowsUsed__c = System.Limits.getDmlRows();
- this.logEntryEvent.LimitsDmlStatementsUsed__c = System.Limits.getDmlStatements();
- this.logEntryEvent.LimitsEmailInvocationsUsed__c = System.Limits.getEmailInvocations();
- this.logEntryEvent.LimitsFutureCallsUsed__c = System.Limits.getFutureCalls();
- this.logEntryEvent.LimitsHeapSizeUsed__c = System.Limits.getHeapSize();
- this.logEntryEvent.LimitsMobilePushApexCallsUsed__c = System.Limits.getMobilePushApexCalls();
- this.logEntryEvent.LimitsQueueableJobsUsed__c = System.Limits.getQueueableJobs();
- this.logEntryEvent.LimitsPublishImmediateDmlStatementsUsed__c = System.Limits.getPublishImmediateDML();
- this.logEntryEvent.LimitsSoqlQueriesUsed__c = System.Limits.getQueries();
- this.logEntryEvent.LimitsSoqlQueryLocatorRowsUsed__c = System.Limits.getQueryLocatorRows();
- this.logEntryEvent.LimitsSoqlQueryRowsUsed__c = System.Limits.getQueryRows();
- this.logEntryEvent.LimitsSoslSearchesUsed__c = System.Limits.getSoslQueries();
- }
-
- this.parseStackTrace();
+ this.logEntryEvent = getLogEntryEventTemplate(entryLoggingLevel);
+ this.setTimestamp(System.now());
+ }
}
/**
@@ -198,7 +153,7 @@ global with sharing class LogEntryEventBuilder {
global LogEntryEventBuilder setMessage(String message) {
// To help with debugging unit tests, always run System.debug statement in a test context
if ((this.shouldSave() || System.Test.isRunningTest()) && this.logEntryEvent != null) {
- String truncatedMessage = truncateFieldValue(Schema.LogEntryEvent__e.Message__c, message);
+ String truncatedMessage = LoggerDataStore.truncateFieldValue(Schema.LogEntryEvent__e.Message__c, message);
Boolean messageTruncated = String.isNotBlank(message) && message.length() > Schema.LogEntryEvent__e.Message__c.getDescribe().getLength();
String cleanedMessage = applyDataMaskRules(this.userSettings.IsDataMaskingEnabled__c, truncatedMessage);
Boolean messageMasked = cleanedMessage != truncatedMessage;
@@ -221,7 +176,7 @@ global with sharing class LogEntryEventBuilder {
return this;
}
- this.logEntryEvent.ExceptionMessage__c = apexException.getMessage();
+ this.logEntryEvent.ExceptionMessage__c = LoggerDataStore.truncateFieldValue(Schema.LogEntryEvent__e.ExceptionMessage__c, apexException.getMessage());
this.logEntryEvent.ExceptionType__c = apexException.getTypeName();
LoggerStackTrace exceptionStackTrace = new LoggerStackTrace(apexException);
@@ -229,7 +184,10 @@ global with sharing class LogEntryEventBuilder {
this.logEntryEvent.ExceptionSourceActionName__c = exceptionStackTrace.Source?.ActionName;
this.logEntryEvent.ExceptionSourceApiName__c = exceptionStackTrace.Source?.ApiName;
this.logEntryEvent.ExceptionSourceMetadataType__c = exceptionStackTrace.Source?.MetadataType.name();
- this.logEntryEvent.ExceptionStackTrace__c = exceptionStackTrace.ParsedStackTraceString;
+ this.logEntryEvent.ExceptionStackTrace__c = LoggerDataStore.truncateFieldValue(
+ Schema.LogEntryEvent__e.ExceptionStackTrace__c,
+ exceptionStackTrace.ParsedStackTraceString
+ );
return this;
}
@@ -243,16 +201,11 @@ global with sharing class LogEntryEventBuilder {
* @return The same instance of `LogEntryEventBuilder`, useful for chaining methods
*/
global LogEntryEventBuilder setDatabaseResult(Database.LeadConvertResult leadConvertResult) {
- if (this.shouldSave() == false) {
- return this;
- }
-
- this.logEntryEvent.DatabaseResultCollectionSize__c = 1;
- this.logEntryEvent.DatabaseResultCollectionType__c = 'Single';
- this.logEntryEvent.DatabaseResultJson__c = JSON.serializePretty(leadConvertResult);
- this.logEntryEvent.DatabaseResultType__c = Database.LeadConvertResult.class.getName();
-
- return this.setRecordId(leadConvertResult?.getLeadId());
+ return this.setDatabaseDetails(
+ new List{ leadConvertResult },
+ Database.LeadConvertResult.class,
+ leadConvertResult?.getLeadId()
+ );
}
/**
@@ -261,16 +214,7 @@ global with sharing class LogEntryEventBuilder {
* @return The same instance of `LogEntryEventBuilder`, useful for chaining methods
*/
global LogEntryEventBuilder setDatabaseResult(List leadConvertResults) {
- if (this.shouldSave() == false) {
- return this;
- }
-
- this.logEntryEvent.DatabaseResultCollectionSize__c = leadConvertResults.size();
- this.logEntryEvent.DatabaseResultCollectionType__c = 'List';
- this.logEntryEvent.DatabaseResultJson__c = JSON.serializePretty(leadConvertResults);
- this.logEntryEvent.DatabaseResultType__c = Database.LeadConvertResult.class.getName();
-
- return this;
+ return this.setDatabaseDetails(leadConvertResults, Database.LeadConvertResult.class, null);
}
/**
@@ -279,16 +223,7 @@ global with sharing class LogEntryEventBuilder {
* @return The same instance of `LogEntryEventBuilder`, useful for chaining methods
*/
global LogEntryEventBuilder setDatabaseResult(Database.DeleteResult deleteResult) {
- if (this.shouldSave() == false) {
- return this;
- }
-
- this.logEntryEvent.DatabaseResultCollectionSize__c = 1;
- this.logEntryEvent.DatabaseResultCollectionType__c = 'Single';
- this.logEntryEvent.DatabaseResultJson__c = JSON.serializePretty(deleteResult);
- this.logEntryEvent.DatabaseResultType__c = Database.DeleteResult.class.getName();
-
- return this.setRecordId(deleteResult?.getId());
+ return this.setDatabaseDetails(new List{ deleteResult }, Database.DeleteResult.class, deleteResult?.getId());
}
/**
@@ -297,16 +232,7 @@ global with sharing class LogEntryEventBuilder {
* @return The same instance of `LogEntryEventBuilder`, useful for chaining methods
*/
global LogEntryEventBuilder setDatabaseResult(Database.MergeResult mergeResult) {
- if (this.shouldSave() == false) {
- return this;
- }
-
- this.logEntryEvent.DatabaseResultCollectionSize__c = 1;
- this.logEntryEvent.DatabaseResultCollectionType__c = 'Single';
- this.logEntryEvent.DatabaseResultJson__c = JSON.serializePretty(mergeResult);
- this.logEntryEvent.DatabaseResultType__c = Database.MergeResult.class.getName();
-
- return this.setRecordId(mergeResult?.getId());
+ return this.setDatabaseDetails(new List{ mergeResult }, Database.MergeResult.class, mergeResult?.getId());
}
/**
@@ -315,16 +241,7 @@ global with sharing class LogEntryEventBuilder {
* @return The same instance of `LogEntryEventBuilder`, useful for chaining methods
*/
global LogEntryEventBuilder setDatabaseResult(Database.SaveResult saveResult) {
- if (this.shouldSave() == false) {
- return this;
- }
-
- this.logEntryEvent.DatabaseResultCollectionSize__c = 1;
- this.logEntryEvent.DatabaseResultCollectionType__c = 'Single';
- this.logEntryEvent.DatabaseResultJson__c = JSON.serializePretty(saveResult);
- this.logEntryEvent.DatabaseResultType__c = Database.SaveResult.class.getName();
-
- return this.setRecordId(saveResult?.getId());
+ return this.setDatabaseDetails(new List{ saveResult }, Database.SaveResult.class, saveResult?.getId());
}
/**
@@ -333,19 +250,12 @@ global with sharing class LogEntryEventBuilder {
* @return The same instance of `LogEntryEventBuilder`, useful for chaining methods
*/
global LogEntryEventBuilder setDatabaseResult(Database.UpsertResult upsertResult) {
- if (this.shouldSave() == false) {
- return this;
+ this.setDatabaseDetails(new List{ upsertResult }, Database.UpsertResult.class, upsertResult?.getId());
+ if (upsertResult != null) {
+ String subtype = upsertResult.isCreated() ? 'Insert' : 'Update';
+ this.logEntryEvent.DatabaseResultType__c += '.' + subtype;
}
-
- // Upsert has 2 subtypes (surprise!) - insert and update - so, UpsertResult has an extra method to take into account
- String subtype = upsertResult.isCreated() ? 'Insert' : 'Update';
-
- this.logEntryEvent.DatabaseResultCollectionSize__c = 1;
- this.logEntryEvent.DatabaseResultCollectionType__c = 'Single';
- this.logEntryEvent.DatabaseResultJson__c = JSON.serializePretty(upsertResult);
- this.logEntryEvent.DatabaseResultType__c = Database.UpsertResult.class.getName() + '.' + subtype;
-
- return this.setRecordId(upsertResult?.getId());
+ return this;
}
/**
@@ -354,16 +264,7 @@ global with sharing class LogEntryEventBuilder {
* @return The same instance of `LogEntryEventBuilder`, useful for chaining methods
*/
global LogEntryEventBuilder setDatabaseResult(Database.UndeleteResult undeleteResult) {
- if (this.shouldSave() == false) {
- return this;
- }
-
- this.logEntryEvent.DatabaseResultCollectionSize__c = 1;
- this.logEntryEvent.DatabaseResultCollectionType__c = 'Single';
- this.logEntryEvent.DatabaseResultJson__c = JSON.serializePretty(undeleteResult);
- this.logEntryEvent.DatabaseResultType__c = Database.UndeleteResult.class.getName();
-
- return this.setRecordId(undeleteResult?.getId());
+ return this.setDatabaseDetails(new List{ undeleteResult }, Database.UndeleteResult.class, undeleteResult?.getId());
}
/**
@@ -372,16 +273,7 @@ global with sharing class LogEntryEventBuilder {
* @return The same instance of `LogEntryEventBuilder`, useful for chaining methods
*/
global LogEntryEventBuilder setDatabaseResult(List deleteResults) {
- if (this.shouldSave() == false) {
- return this;
- }
-
- this.logEntryEvent.DatabaseResultCollectionSize__c = deleteResults?.size();
- this.logEntryEvent.DatabaseResultCollectionType__c = 'List';
- this.logEntryEvent.DatabaseResultJson__c = JSON.serializePretty(deleteResults);
- this.logEntryEvent.DatabaseResultType__c = Database.DeleteResult.class.getName();
-
- return this;
+ return this.setDatabaseDetails(deleteResults, Database.DeleteResult.class, null);
}
/**
@@ -390,16 +282,7 @@ global with sharing class LogEntryEventBuilder {
* @return The same instance of `LogEntryEventBuilder`, useful for chaining methods
*/
global LogEntryEventBuilder setDatabaseResult(List mergeResults) {
- if (this.shouldSave() == false) {
- return this;
- }
-
- this.logEntryEvent.DatabaseResultCollectionSize__c = mergeResults?.size();
- this.logEntryEvent.DatabaseResultCollectionType__c = 'List';
- this.logEntryEvent.DatabaseResultJson__c = JSON.serializePretty(mergeResults);
- this.logEntryEvent.DatabaseResultType__c = Database.MergeResult.class.getName();
-
- return this;
+ return this.setDatabaseDetails(mergeResults, Database.MergeResult.class, null);
}
/**
@@ -408,16 +291,7 @@ global with sharing class LogEntryEventBuilder {
* @return The same instance of `LogEntryEventBuilder`, useful for chaining methods
*/
global LogEntryEventBuilder setDatabaseResult(List saveResults) {
- if (this.shouldSave() == false) {
- return this;
- }
-
- this.logEntryEvent.DatabaseResultCollectionSize__c = saveResults?.size();
- this.logEntryEvent.DatabaseResultCollectionType__c = 'List';
- this.logEntryEvent.DatabaseResultJson__c = JSON.serializePretty(saveResults);
- this.logEntryEvent.DatabaseResultType__c = Database.SaveResult.class.getName();
-
- return this;
+ return this.setDatabaseDetails(saveResults, Database.SaveResult.class, null);
}
/**
@@ -426,16 +300,7 @@ global with sharing class LogEntryEventBuilder {
* @return The same instance of `LogEntryEventBuilder`, useful for chaining methods
*/
global LogEntryEventBuilder setDatabaseResult(List upsertResults) {
- if (this.shouldSave() == false) {
- return this;
- }
-
- this.logEntryEvent.DatabaseResultCollectionSize__c = upsertResults?.size();
- this.logEntryEvent.DatabaseResultCollectionType__c = 'List';
- this.logEntryEvent.DatabaseResultJson__c = JSON.serializePretty(upsertResults);
- this.logEntryEvent.DatabaseResultType__c = Database.UpsertResult.class.getName();
-
- return this;
+ return this.setDatabaseDetails(upsertResults, Database.UpsertResult.class, null);
}
/**
@@ -444,16 +309,7 @@ global with sharing class LogEntryEventBuilder {
* @return The same instance of `LogEntryEventBuilder`, useful for chaining methods
*/
global LogEntryEventBuilder setDatabaseResult(List undeleteResults) {
- if (this.shouldSave() == false) {
- return this;
- }
-
- this.logEntryEvent.DatabaseResultCollectionSize__c = undeleteResults?.size();
- this.logEntryEvent.DatabaseResultCollectionType__c = 'List';
- this.logEntryEvent.DatabaseResultJson__c = JSON.serializePretty(undeleteResults);
- this.logEntryEvent.DatabaseResultType__c = Database.UndeleteResult.class.getName();
-
- return this;
+ return this.setDatabaseDetails(undeleteResults, Database.UndeleteResult.class, null);
}
/**
@@ -514,7 +370,7 @@ global with sharing class LogEntryEventBuilder {
this.logEntryEvent.RecordCollectionSize__c = 1;
this.logEntryEvent.RecordCollectionType__c = 'Single';
- String truncatedRecordJson = truncateFieldValue(
+ String truncatedRecordJson = LoggerDataStore.truncateFieldValue(
Schema.LogEntryEvent__e.RecordJson__c,
getJson(record, this.userSettings.IsRecordFieldStrippingEnabled__c)
);
@@ -551,7 +407,7 @@ global with sharing class LogEntryEventBuilder {
this.logEntryEvent.RecordCollectionType__c = 'List';
Schema.SObjectType sobjectType = records?.getSObjectType();
- String truncatedRecordJson = truncateFieldValue(
+ String truncatedRecordJson = LoggerDataStore.truncateFieldValue(
Schema.LogEntryEvent__e.RecordJson__c,
getJson(records, this.userSettings.IsRecordFieldStrippingEnabled__c)
);
@@ -588,7 +444,7 @@ global with sharing class LogEntryEventBuilder {
List recordsList = recordIdToRecord?.values();
Schema.SObjectType sobjectType = recordIdToRecord?.size() > 0 ? recordsList.get(0)?.getSObjectType() : null;
- String truncatedRecordJson = truncateFieldValue(
+ String truncatedRecordJson = LoggerDataStore.truncateFieldValue(
Schema.LogEntryEvent__e.RecordJson__c,
getJson(recordIdToRecord, this.userSettings.IsRecordFieldStrippingEnabled__c)
);
@@ -620,7 +476,7 @@ global with sharing class LogEntryEventBuilder {
return this;
}
- String truncatedRequestBody = truncateFieldValue(Schema.LogEntryEvent__e..HttpRequestBody__c, request.getBody());
+ String truncatedRequestBody = LoggerDataStore.truncateFieldValue(Schema.LogEntryEvent__e.HttpRequestBody__c, request.getBody());
String cleanedRequestBody = applyDataMaskRules(this.userSettings.IsDataMaskingEnabled__c, truncatedRequestBody);
Boolean requestBodyMasked = cleanedRequestBody != truncatedRequestBody;
@@ -642,7 +498,7 @@ global with sharing class LogEntryEventBuilder {
return this;
}
- String truncatedResponseBody = truncateFieldValue(Schema.LogEntryEvent__e..HttpRequestBody__c, response.getBody());
+ String truncatedResponseBody = LoggerDataStore.truncateFieldValue(Schema.LogEntryEvent__e.HttpResponseBody__c, response.getBody());
String cleanedResponseBody = applyDataMaskRules(this.userSettings.IsDataMaskingEnabled__c, truncatedResponseBody);
Boolean responseBodyMasked = cleanedResponseBody != truncatedResponseBody;
@@ -657,7 +513,7 @@ global with sharing class LogEntryEventBuilder {
this.logEntryEvent.HttpResponseBody__c = cleanedResponseBody;
this.logEntryEvent.HttpResponseBodyMasked__c = responseBodyMasked;
this.logEntryEvent.HttpResponseHeaderKeys__c = String.join(response.getHeaderKeys(), NEW_LINE_DELIMITER);
- this.logEntryEvent.HttpResponseHeaders__c = formattedHeadersString;
+ this.logEntryEvent.HttpResponseHeaders__c = LoggerDataStore.truncateFieldValue(Schema.LogEntryEvent__e.HttpResponseHeaders__c, formattedHeadersString);
this.logEntryEvent.HttpResponseStatus__c = response.getStatus();
this.logEntryEvent.HttpResponseStatusCode__c = response.getStatusCode();
return this;
@@ -674,7 +530,7 @@ global with sharing class LogEntryEventBuilder {
}
String stringifiedRequestBody = request.requestBody?.toString();
- String truncatedRequestBody = truncateFieldValue(Schema.LogEntryEvent__e..RestRequestBody__c, stringifiedRequestBody);
+ String truncatedRequestBody = LoggerDataStore.truncateFieldValue(Schema.LogEntryEvent__e.RestRequestBody__c, stringifiedRequestBody);
String cleanedRequestBody = applyDataMaskRules(this.userSettings.IsDataMaskingEnabled__c, truncatedRequestBody);
Boolean requestBodyMasked = cleanedRequestBody != truncatedRequestBody;
@@ -702,7 +558,7 @@ global with sharing class LogEntryEventBuilder {
this.logEntryEvent.RestRequestBody__c = cleanedRequestBody;
this.logEntryEvent.RestRequestBodyMasked__c = requestBodyMasked;
this.logEntryEvent.RestRequestHeaderKeys__c = formattedHeaderKeysString;
- this.logEntryEvent.RestRequestHeaders__c = formattedHeadersString;
+ this.logEntryEvent.RestRequestHeaders__c = LoggerDataStore.truncateFieldValue(Schema.LogEntryEvent__e.RestRequestHeaders__c, formattedHeadersString);
this.logEntryEvent.RestRequestMethod__c = request.httpMethod;
this.logEntryEvent.RestRequestParameters__c = formattedParametersString;
this.logEntryEvent.RestRequestRemoteAddress__c = request.remoteAddress;
@@ -722,7 +578,7 @@ global with sharing class LogEntryEventBuilder {
}
String stringifiedResponseBody = response.responseBody?.toString();
- String truncatedResponseBody = truncateFieldValue(Schema.LogEntryEvent__e.RestResponseBody__c, stringifiedResponseBody);
+ String truncatedResponseBody = LoggerDataStore.truncateFieldValue(Schema.LogEntryEvent__e.RestResponseBody__c, stringifiedResponseBody);
String cleanedResponseBody = applyDataMaskRules(this.userSettings.IsDataMaskingEnabled__c, truncatedResponseBody);
Boolean responseBodyMasked = cleanedResponseBody != truncatedResponseBody;
@@ -742,7 +598,7 @@ global with sharing class LogEntryEventBuilder {
this.logEntryEvent.RestResponseBody__c = cleanedResponseBody;
this.logEntryEvent.RestResponseBodyMasked__c = responseBodyMasked;
this.logEntryEvent.RestResponseHeaderKeys__c = formattedHeaderKeysString;
- this.logEntryEvent.RestResponseHeaders__c = formattedHeadersString;
+ this.logEntryEvent.RestResponseHeaders__c = LoggerDataStore.truncateFieldValue(Schema.LogEntryEvent__e.RestResponseHeaders__c, formattedHeadersString);
this.logEntryEvent.RestResponseStatusCode__c = response.statusCode;
return this;
}
@@ -810,6 +666,38 @@ global with sharing class LogEntryEventBuilder {
return this.shouldSave;
}
+ /**
+ * @description **This is only intended to be used internally by Nebula Logger, and is subject to change.**
+ * @param loggingContext Variables specific to the current Logger state
+ * Sets information only available from the `Logger` class.
+ */
+ public void setLoggingContext(LoggingContext loggingContext) {
+ this.logEntryEvent.ApiVersion__c = loggingContext.organizationApiVersion;
+ this.logEntryEvent.EntryScenario__c = loggingContext.currentEntryScenario;
+ this.logEntryEvent.LoggerVersionNumber__c = loggingContext.loggerVersionNumber;
+ this.logEntryEvent.OrganizationApiVersion__c = loggingContext.organizationApiVersion;
+ this.logEntryEvent.OrganizationDomainUrl__c = loggingContext.organizationDomainUrl;
+ this.logEntryEvent.RequestId__c = loggingContext.requestId;
+ this.logEntryEvent.SystemMode__c = loggingContext.systemMode?.name();
+ this.logEntryEvent.TransactionEntryNumber__c = loggingContext.entryNumber;
+ this.logEntryEvent.TransactionId__c = loggingContext.transactionId;
+ this.logEntryEvent.UserLoggingLevel__c = loggingContext.userLoggingLevel.name();
+ this.logEntryEvent.UserLoggingLevelOrdinal__c = loggingContext.userLoggingLevel.ordinal();
+ }
+
+ /**
+ * @description **This is only intended to be used internally by Nebula Logger, and is subject to change.**
+ * @param timestamp Datetime instance to set timestamp fields on this.logEntryEvent
+ */
+ public void setTimestamp(Datetime timestamp) {
+ // Salesforce does not provide precise datetimes in Apex triggers for platform events
+ // Set the string value of timestamp to a second field as a workaround
+ // See https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_events_api_considerations.htm
+ this.logEntryEvent.EpochTimestamp__c = timestamp.getTime();
+ this.logEntryEvent.Timestamp__c = timestamp;
+ this.logEntryEvent.TimestampString__c = '' + this.logEntryEvent.EpochTimestamp__c;
+ }
+
/**
* @description Returns the `LogEntryEvent__e` record for this instance of LogEntryEventBuilder
* @return The `LogEntryEvent__e` record
@@ -821,9 +709,6 @@ global with sharing class LogEntryEventBuilder {
// Lazy-loading of some details to help minimize Apex heap size usage until needed
if (this.detailsAreSet == false) {
- for (String fieldName : LOG_ENTRY_EVENT_TEMPLATE.getPopulatedFieldsAsMap().keySet()) {
- this.logEntryEvent.put(fieldName, LOG_ENTRY_EVENT_TEMPLATE.get(fieldName));
- }
if (this.userSettings.IsAnonymousModeEnabled__c == false) {
setUserInfoDetails(this.logEntryEvent);
setQueriedAuthSessionDetails(this.logEntryEvent, this.userSettings);
@@ -843,13 +728,6 @@ global with sharing class LogEntryEventBuilder {
this.tagDetailsAreSet = true;
}
- // Salesforce does not provide precise datetimes in Apex triggers for platform events
- // Set the string value of timestamp to a second field as a workaround
- // See https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_events_api_considerations.htm
- if (this.logEntryEvent.Timestamp__c != null) {
- this.logEntryEvent.TimestampString__c = String.valueOf(this.logEntryEvent.Timestamp__c.getTime());
- }
-
return this.logEntryEvent;
}
@@ -883,14 +761,6 @@ global with sharing class LogEntryEventBuilder {
System.debug(this.entryLoggingLevel, this.debugMessage);
}
- private void parseStackTrace() {
- if (this.shouldSave() == false || LoggerParameter.ENABLE_STACK_TRACE_PARSING == false) {
- return;
- }
-
- this.parseStackTrace(new LoggerStackTrace());
- }
-
private LogEntryEventBuilder parseStackTrace(LoggerStackTrace originStackTrace) {
if (this.shouldSave() == false || LoggerParameter.ENABLE_STACK_TRACE_PARSING == false) {
return this;
@@ -900,10 +770,24 @@ global with sharing class LogEntryEventBuilder {
this.logEntryEvent.OriginSourceActionName__c = originStackTrace.Source?.ActionName;
this.logEntryEvent.OriginSourceApiName__c = originStackTrace.Source?.ApiName;
this.logEntryEvent.OriginSourceMetadataType__c = originStackTrace.Source?.MetadataType.name();
- this.logEntryEvent.StackTrace__c = originStackTrace.ParsedStackTraceString;
+ this.logEntryEvent.StackTrace__c = LoggerDataStore.truncateFieldValue(Schema.LogEntryEvent__e.StackTrace__c, originStackTrace.ParsedStackTraceString);
return this;
}
+ private LogEntryEventBuilder setDatabaseDetails(List