diff --git a/.github/codecov.yml b/.github/codecov.yml
index a8d661d84..1e2ea9854 100644
--- a/.github/codecov.yml
+++ b/.github/codecov.yml
@@ -7,6 +7,7 @@ coverage:
if_ci_failed: success
patch: off
ignore:
+ - 'config/experience-cloud/**/*'
- 'nebula-logger/recipes/**/*'
comment:
behavior: new
diff --git a/README.md b/README.md
index 3240f8512..a6db30db9 100644
--- a/README.md
+++ b/README.md
@@ -5,10 +5,10 @@
The most robust logger for Salesforce. Works with Apex, Lightning Components, Flow, Process Builder & Integrations. Designed for Salesforce admins, developers & architects.
-## Unlocked Package - v4.9.4
+## Unlocked Package - v4.9.5
-[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000023R8WQAU)
-[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000023R8WQAU)
+[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000023R9KQAU)
+[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000023R9KQAU)
[![View Documentation](./images/btn-view-documentation.png)](https://jongpie.github.io/NebulaLogger/)
## Managed Package - v4.9.0
diff --git a/docs/apex/Configuration/LoggerParameter.md b/docs/apex/Configuration/LoggerParameter.md
index b2ca7f7c4..03e23fa3e 100644
--- a/docs/apex/Configuration/LoggerParameter.md
+++ b/docs/apex/Configuration/LoggerParameter.md
@@ -26,6 +26,10 @@ Indicates if Nebula Logger will append its own log entries about the logging sys
Indicates if Nebula Logger's tagging system is enabled. Controlled by the custom metadata record `LoggerParameter.EnableTagging`, or `true` as the default
+#### `PLATFORM_CACHE_PARTITION_NAME` → `String`
+
+The name of the Platform Cache partition to use for caching (when platform cache is enabled). Controlled by the custom metadata record `LoggerParameter.PlatformCachePartitionName`, or `LoggerCache` as the default
+
#### `QUERY_APEX_CLASS_DATA` → `Boolean`
Controls if Nebula Logger queries `ApexClass` data. When set to `false`, any `ApexClass` fields on `LogEntryEvent__e` and `Log__c` will not be populated Controlled by the custom metadata record `LoggerParameter.QueryApexClassData`, or `true` as the default
diff --git a/nebula-logger/core/main/configuration/classes/LoggerCache.cls b/nebula-logger/core/main/configuration/classes/LoggerCache.cls
index 26d8e0483..ef8a83145 100644
--- a/nebula-logger/core/main/configuration/classes/LoggerCache.cls
+++ b/nebula-logger/core/main/configuration/classes/LoggerCache.cls
@@ -9,23 +9,32 @@
*/
@SuppressWarnings('PMD.ExcessivePublicCount')
public without sharing class LoggerCache {
+ private static final String DEFAULT_PARTITION_NAME = 'LoggerCache';
private static final Boolean PLATFORM_CACHE_IS_IMMUTABLE = false;
@TestVisible
private static final String PLATFORM_CACHE_NULL_VALUE = '<{(CACHE_VALUE_IS_NULL)}>'; // Presumably, no one will ever use this as an actual value
@TestVisible
- private static final String PLATFORM_CACHE_PARTITION_NAME = getQualifiedParitionName('LoggerCache');
+ private static final String PLATFORM_CACHE_PARTITION_NAME = getQualifiedParitionName(LoggerParameter.PLATFORM_CACHE_PARTITION_NAME);
private static final Cache.Visibility PLATFORM_CACHE_VISIBILITY = Cache.Visibility.NAMESPACE;
private static final TransactionCache TRANSACTION_CACHE_INSTANCE = new TransactionCache();
private static PlatformCachePartitionDelegate organizationPartitionDelegate = new PlatformCachePartitionDelegate(
- Cache.Org.getPartition(PLATFORM_CACHE_PARTITION_NAME)
+ PlatformCachePartitionType.ORGANIZATION,
+ PLATFORM_CACHE_PARTITION_NAME
);
private static PlatformCachePartitionDelegate sessionPartitionDelegate = new PlatformCachePartitionDelegate(
- Cache.Session.getPartition(PLATFORM_CACHE_PARTITION_NAME)
+ PlatformCachePartitionType.SESSION,
+ PLATFORM_CACHE_PARTITION_NAME
);
private static PlatformCache organizationCacheInstance;
private static PlatformCache sessionCacheInstance;
+ @TestVisible
+ private enum PlatformCachePartitionType {
+ ORGANIZATION,
+ SESSION
+ }
+
/**
* @description Interface used to define caches that can be used to store values via different mechanisms
*/
@@ -96,8 +105,15 @@ public without sharing class LoggerCache {
}
private static String getQualifiedParitionName(String unqualifiedPartitionName) {
- String className = LoggerCache.class.getName();
- String namespacePrefix = className.contains('.') ? className.substringBefore('.') + '.' : '';
+ // Since Nebula Logger includes a cache partition (LoggerCache), the included partition's name
+ // needs to be qualified in order to work in the managed package. If a custom partition is being
+ // used instead, then just use the partition name as-is - no namespace is added.
+ if (unqualifiedPartitionName != DEFAULT_PARTITION_NAME) {
+ return unqualifiedPartitionName;
+ }
+
+ String qualifiedClassName = LoggerCache.class.getName();
+ String namespacePrefix = qualifiedClassName.contains('.') ? qualifiedClassName.substringBefore('.') + '.' : '';
return namespacePrefix + unqualifiedPartitionName;
}
@@ -115,34 +131,51 @@ public without sharing class LoggerCache {
* @description Manages interacting with platform cache partitions, and can be mocked during unit tests
* so that tests don't have to rely on the actual platform cache partitions configured in the org.
*/
- @SuppressWarnings('PMD.ApexDoc')
+ @SuppressWarnings('PMD.ApexDoc, PMD.EmptyCatchBlock')
@TestVisible
private virtual class PlatformCachePartitionDelegate {
private final Cache.Partition platformCachePartition;
- protected PlatformCachePartitionDelegate(Cache.Partition platformCachePartition) {
- this.platformCachePartition = platformCachePartition;
+ protected PlatformCachePartitionDelegate(PlatformCachePartitionType partitionType, String partitionName) {
+ // Since orgs can customize the platform cache partition (via LoggerParameter__mdt.PlatformCachePartitionName),
+ // some orgs could have problematic configurations (or may have even deleted the included LoggerCache partition),
+ // and it seems better to eat the exceptions & fallback to the transaction cache (which doesn't rely on Platform Cache).
+ // The alternative is a runtime exception, which isn't ideal.
+ try {
+ switch on partitionType {
+ when ORGANIZATION {
+ this.platformCachePartition = Cache.Org.getPartition(partitionName);
+ }
+ when SESSION {
+ this.platformCachePartition = Cache.Session.getPartition(partitionName);
+ }
+ }
+ } catch (Cache.Org.OrgCacheException orgCacheException) {
+ // No-op if the partition can't be found - the rest of the code will fallback to using the transaction cache
+ } catch (Cache.Session.SessionCacheException sessionCacheException) {
+ // No-op if the partition can't be found - the rest of the code will fallback to using the transaction cache
+ }
}
public virtual Boolean contains(String key) {
- return this.platformCachePartition.contains(key);
+ return this.platformCachePartition != null && this.platformCachePartition.contains(key);
}
public virtual Object get(String key) {
- return this.platformCachePartition.get(key);
+ return this.platformCachePartition?.get(key);
}
public virtual Boolean isAvailable() {
- return this.platformCachePartition.isAvailable();
+ return this.platformCachePartition != null && this.platformCachePartition.isAvailable();
}
@SuppressWarnings('PMD.ExcessiveParameterList')
public virtual void put(String key, Object value, Integer cacheTtlSeconds, Cache.Visibility cacheVisiblity, Boolean isCacheImmutable) {
- this.platformCachePartition.put(key, value, cacheTtlSeconds, cacheVisiblity, isCacheImmutable);
+ this.platformCachePartition?.put(key, value, cacheTtlSeconds, cacheVisiblity, isCacheImmutable);
}
public virtual void remove(String key) {
- this.platformCachePartition.remove(key);
+ this.platformCachePartition?.remove(key);
}
}
@@ -152,7 +185,6 @@ public without sharing class LoggerCache {
*/
@SuppressWarnings('PMD.ApexDoc')
private class PlatformCache implements Cacheable {
- private final Cache.Partition cachePartition;
private final PlatformCachePartitionDelegate cachePartitionDelegate;
private final Integer cacheTtlSeconds;
private final Cacheable transactionCache;
diff --git a/nebula-logger/core/main/configuration/classes/LoggerParameter.cls b/nebula-logger/core/main/configuration/classes/LoggerParameter.cls
index 25e85a4f9..01d8ca507 100644
--- a/nebula-logger/core/main/configuration/classes/LoggerParameter.cls
+++ b/nebula-logger/core/main/configuration/classes/LoggerParameter.cls
@@ -73,6 +73,20 @@ public class LoggerParameter {
private set;
}
+ /**
+ * @description The name of the Platform Cache partition to use for caching (when platform cache is enabled).
+ * Controlled by the custom metadata record `LoggerParameter.PlatformCachePartitionName`, or `LoggerCache` as the default
+ */
+ public static final String PLATFORM_CACHE_PARTITION_NAME {
+ get {
+ if (PLATFORM_CACHE_PARTITION_NAME == null) {
+ PLATFORM_CACHE_PARTITION_NAME = getString('PlatformCachePartitionName', 'LoggerCache');
+ }
+ return PLATFORM_CACHE_PARTITION_NAME;
+ }
+ private set;
+ }
+
/**
* @description Controls if Nebula Logger queries `ApexClass` data.
* When set to `false`, any `ApexClass` fields on `LogEntryEvent__e` and `Log__c` will not be populated
diff --git a/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.PlatformCachePartitionName.md-meta.xml b/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.PlatformCachePartitionName.md-meta.xml
new file mode 100644
index 000000000..95581e200
--- /dev/null
+++ b/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.PlatformCachePartitionName.md-meta.xml
@@ -0,0 +1,19 @@
+
+
+
+ false
+
+ Description__c
+ Indicates the name of the Platform Cache partition to use for caching (when platform cache is enabled).
+
+By default, the included cache partition 'LoggerCache' is used.
+
+
+ Value__c
+ LoggerCache
+
+
diff --git a/nebula-logger/core/main/logger-engine/classes/Logger.cls b/nebula-logger/core/main/logger-engine/classes/Logger.cls
index 838d8803d..ff0cb326f 100644
--- a/nebula-logger/core/main/logger-engine/classes/Logger.cls
+++ b/nebula-logger/core/main/logger-engine/classes/Logger.cls
@@ -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.9.4';
+ private static final String CURRENT_VERSION_NUMBER = 'v4.9.5';
private static final System.LoggingLevel DEFAULT_LOGGING_LEVEL = System.LoggingLevel.DEBUG;
private static final Set IGNORED_APEX_CLASSES = initializeIgnoredApexClasses();
private static final List LOG_ENTRIES_BUFFER = new List();
diff --git a/nebula-logger/core/tests/configuration/classes/LoggerCache_Tests.cls b/nebula-logger/core/tests/configuration/classes/LoggerCache_Tests.cls
index 8f599519f..86f75e9f5 100644
--- a/nebula-logger/core/tests/configuration/classes/LoggerCache_Tests.cls
+++ b/nebula-logger/core/tests/configuration/classes/LoggerCache_Tests.cls
@@ -369,7 +369,7 @@ private class LoggerCache_Tests {
public Integer removeMethodCallCount = 0;
private MockPlatformCachePartitionDelegate(Boolean isAvailable) {
- super(null);
+ super(null, null);
this.isAvailable = isAvailable;
}
diff --git a/nebula-logger/core/tests/configuration/classes/LoggerParameter_Tests.cls b/nebula-logger/core/tests/configuration/classes/LoggerParameter_Tests.cls
index fd20986c8..ddc71c154 100644
--- a/nebula-logger/core/tests/configuration/classes/LoggerParameter_Tests.cls
+++ b/nebula-logger/core/tests/configuration/classes/LoggerParameter_Tests.cls
@@ -68,6 +68,17 @@ private class LoggerParameter_Tests {
System.Assert.areEqual(mockValue, returnedValue);
}
+ @IsTest
+ static void it_should_return_constant_value_for_platform_cache_partition_name() {
+ String mockValue = 'SomeValue';
+ LoggerParameter__mdt mockParameter = new LoggerParameter__mdt(DeveloperName = 'PlatformCachePartitionName', Value__c = mockValue);
+ LoggerParameter.setMock(mockParameter);
+
+ String returnedValue = LoggerParameter.PLATFORM_CACHE_PARTITION_NAME;
+
+ System.Assert.areEqual(mockValue, returnedValue);
+ }
+
@IsTest
static void it_should_return_constant_value_for_query_apex_class_data() {
Boolean mockValue = false;
diff --git a/nebula-logger/extra-tests/tests/LoggerCache_Tests_PlatformCache.cls b/nebula-logger/extra-tests/tests/LoggerCache_Tests_PlatformCache.cls
index bd47e21dd..3b0f9aabe 100644
--- a/nebula-logger/extra-tests/tests/LoggerCache_Tests_PlatformCache.cls
+++ b/nebula-logger/extra-tests/tests/LoggerCache_Tests_PlatformCache.cls
@@ -13,6 +13,92 @@
@SuppressWarnings('PMD.ApexDoc, PMD.ApexAssertionsShouldIncludeMessage, PMD.CyclomaticComplexity, PMD.MethodNamingConventions')
@IsTest(IsParallel=true)
private class LoggerCache_Tests_PlatformCache {
+ @IsTest
+ static void it_gracefully_handles_nonexistent_platform_cache_partition_name_for_organization_cache() {
+ LoggerParameter__mdt platformCachePartitionNameParameter = new LoggerParameter__mdt(
+ DeveloperName = 'PlatformCachePartitionName',
+ Value__c = 'SomeValueThatWillHopefullyAndProbablyNeverExistForAPlatformCachePartitionName'
+ );
+ LoggerParameter.setMock(platformCachePartitionNameParameter);
+
+ LoggerCache.Cacheable organizationCache = LoggerCache.getOrganizationCache();
+
+ // Run the different Cacheable methods to ensure everything works
+ String testKey = 'someKey';
+ Object testValue = System.now();
+ System.Assert.isNotNull(organizationCache);
+ System.Assert.isFalse(organizationCache.contains(testKey));
+ System.Assert.areEqual(
+ organizationCache.contains(testKey),
+ LoggerCache.getTransactionCache().contains(testKey),
+ 'Organization and transaction cache should be in sync'
+ );
+ System.Assert.isNull(organizationCache.get(testKey));
+ System.Assert.areEqual(
+ organizationCache.get(testKey),
+ LoggerCache.getTransactionCache().get(testKey),
+ 'Organization and transaction cache should be in sync'
+ );
+ organizationCache.remove(testKey);
+ organizationCache.put(testKey, testValue);
+ System.Assert.isTrue(organizationCache.contains(testKey));
+ System.Assert.areEqual(
+ organizationCache.contains(testKey),
+ LoggerCache.getTransactionCache().contains(testKey),
+ 'Organization and transaction cache should be in sync'
+ );
+ System.Assert.areEqual(testValue, organizationCache.get(testKey));
+ System.Assert.areEqual(
+ organizationCache.get(testKey),
+ LoggerCache.getTransactionCache().get(testKey),
+ 'Organization and transaction cache should be in sync'
+ );
+ organizationCache.remove(testKey);
+ System.Assert.isNull(organizationCache.get(testKey));
+ System.Assert.areEqual(
+ organizationCache.get(testKey),
+ LoggerCache.getTransactionCache().get(testKey),
+ 'Organization and transaction cache should be in sync'
+ );
+ }
+
+ @IsTest
+ static void it_gracefully_handles_nonexistent_platform_cache_partition_name_for_session_cache() {
+ LoggerParameter__mdt platformCachePartitionNameParameter = new LoggerParameter__mdt(
+ DeveloperName = 'PlatformCachePartitionName',
+ Value__c = 'SomeValueThatWillHopefullyAndProbablyNeverExistForAPlatformCachePartitionName'
+ );
+ LoggerParameter.setMock(platformCachePartitionNameParameter);
+
+ LoggerCache.Cacheable sessionCache = LoggerCache.getSessionCache();
+
+ // Run the different Cacheable methods to ensure everything works
+ String testKey = 'someKey';
+ Object testValue = System.now();
+ System.Assert.isNotNull(sessionCache);
+ System.Assert.isFalse(sessionCache.contains(testKey));
+ System.Assert.areEqual(
+ sessionCache.contains(testKey),
+ LoggerCache.getTransactionCache().contains(testKey),
+ 'Session and transaction cache should be in sync'
+ );
+ System.Assert.isNull(sessionCache.get(testKey));
+ System.Assert.areEqual(sessionCache.get(testKey), LoggerCache.getTransactionCache().get(testKey), 'Session and transaction cache should be in sync');
+ sessionCache.remove(testKey);
+ sessionCache.put(testKey, testValue);
+ System.Assert.isTrue(sessionCache.contains(testKey));
+ System.Assert.areEqual(
+ sessionCache.contains(testKey),
+ LoggerCache.getTransactionCache().contains(testKey),
+ 'Session and transaction cache should be in sync'
+ );
+ System.Assert.areEqual(testValue, sessionCache.get(testKey));
+ System.Assert.areEqual(sessionCache.get(testKey), LoggerCache.getTransactionCache().get(testKey), 'Session and transaction cache should be in sync');
+ sessionCache.remove(testKey);
+ System.Assert.isNull(sessionCache.get(testKey));
+ System.Assert.areEqual(sessionCache.get(testKey), LoggerCache.getTransactionCache().get(testKey), 'Session and transaction cache should be in sync');
+ }
+
@IsTest
static void it_adds_new_key_to_organization_and_transaction_cache() {
String mockKey = 'SomeKey';
diff --git a/package.json b/package.json
index b346a1cd9..df37bc54a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "nebula-logger",
- "version": "4.9.4",
+ "version": "4.9.5",
"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",
diff --git a/sfdx-project.json b/sfdx-project.json
index 441a97302..0af76f2e3 100644
--- a/sfdx-project.json
+++ b/sfdx-project.json
@@ -14,9 +14,9 @@
"package": "Nebula Logger - Core",
"path": "./nebula-logger/core",
"definitionFile": "./config/scratch-orgs/base-scratch-def.json",
- "versionNumber": "4.9.4.NEXT",
- "versionName": "New Indicator Icons",
- "versionDescription": "Updated formula fields on LogEntry__c to replace old flag icons with emojis that are more visually distinct from one another, updated format of LogEntry__c limits formula fields to display percentage first (after new indicators) with limit used & max fields displayed in parenthesis",
+ "versionNumber": "4.9.5.NEXT",
+ "versionName": "Configurable Platform Cache Partition Name",
+ "versionDescription": "Added new CMDT record LoggerParameter.PlatformCachePartitionName to control which platform cache partition is used for caching",
"releaseNotesUrl": "https://github.com/jongpie/NebulaLogger/releases",
"unpackagedMetadata": {
"path": "./nebula-logger/extra-tests"
@@ -148,6 +148,7 @@
"Nebula Logger - Core@4.9.2-enhancements-for-log-entry-event-stream-lwc": "04t5Y0000023R7iQAE",
"Nebula Logger - Core@4.9.3-new-indicator-icons": "04t5Y0000023R7sQAE",
"Nebula Logger - Core@4.9.4-new-indicator-icons": "04t5Y0000023R8WQAU",
+ "Nebula Logger - Core@4.9.5-configurable-platform-cache-partition-name": "04t5Y0000023R9KQAU",
"Nebula Logger - Plugin - Async Failure Additions": "0Ho5Y000000blO4SAI",
"Nebula Logger - Plugin - Async Failure Additions@1.0.0": "04t5Y0000015lhiQAA",
"Nebula Logger - Plugin - Async Failure Additions@1.0.1": "04t5Y0000015lhsQAA",