Skip to content

Commit

Permalink
Configurable Platform Cache partition name (#415)
Browse files Browse the repository at this point in the history
* Added new CMDT record LoggerParameter.PlatformCachePartitionName to control which platform cache partition is used for caching

* Updated LoggerCache to gracefully handle a nonexistent Platform Cache partition (it internally falls back to using the transaction cache, which doesn't rely on Platform Cache)

* Updated codecov.yml to ignore sample metadata used for creating an Experience Cloud site
  • Loading branch information
jongpie authored Nov 29, 2022
1 parent e6448bd commit ebe5796
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 23 deletions.
1 change: 1 addition & 0 deletions .github/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ coverage:
if_ci_failed: success
patch: off
ignore:
- 'config/experience-cloud/**/*'
- 'nebula-logger/recipes/**/*'
comment:
behavior: new
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions docs/apex/Configuration/LoggerParameter.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
60 changes: 46 additions & 14 deletions nebula-logger/core/main/configuration/classes/LoggerCache.cls
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
}

Expand All @@ -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);
}
}

Expand All @@ -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;
Expand Down
14 changes: 14 additions & 0 deletions nebula-logger/core/main/configuration/classes/LoggerParameter.cls
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<CustomMetadata
xmlns="http://soap.sforce.com/2006/04/metadata"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
>
<label>Platform Cache Partition Name</label>
<protected>false</protected>
<values>
<field>Description__c</field>
<value xsi:type="xsd:string">Indicates the name of the Platform Cache partition to use for caching (when platform cache is enabled).

By default, the included cache partition &apos;LoggerCache&apos; is used.</value>
</values>
<values>
<field>Value__c</field>
<value xsi:type="xsd:string">LoggerCache</value>
</values>
</CustomMetadata>
2 changes: 1 addition & 1 deletion nebula-logger/core/main/logger-engine/classes/Logger.cls
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> IGNORED_APEX_CLASSES = initializeIgnoredApexClasses();
private static final List<LogEntryEventBuilder> LOG_ENTRIES_BUFFER = new List<LogEntryEventBuilder>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ private class LoggerCache_Tests {
public Integer removeMethodCallCount = 0;

private MockPlatformCachePartitionDelegate(Boolean isAvailable) {
super(null);
super(null, null);
this.isAvailable = isAvailable;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
7 changes: 4 additions & 3 deletions sfdx-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down

0 comments on commit ebe5796

Please sign in to comment.