Skip to content

Commit

Permalink
WIP: added new plugin 'Log Retention Rules' to close #226
Browse files Browse the repository at this point in the history
This plugin complements the log retention functionality in LoggerSettings__c & the LoggerScenarioRule__mdt objects
The plugin's code is based on my other repo, ApexValidationRules - I've repurposed the core code to be used for configuring log retention rules & conditions
  • Loading branch information
jongpie committed Oct 28, 2021
1 parent 0c9931a commit 03802c1
Show file tree
Hide file tree
Showing 30 changed files with 1,281 additions and 5 deletions.
43 changes: 43 additions & 0 deletions nebula-logger-plugins/Log-Retention-Rules/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Log Retention Rules plugin for Nebula Logger

> :information_source: Requires Nebula Logger v4.6.12 or newer
Adds the ability to create & deploy advanced, configurable rules for setting the retention date of `Log__c` records, using custom metadata types `LogRetentionRule__mdt` and `LogRetentionRuleCondition__mdt`.

[![Install Unlocked Package Plugin](./content/btn-install-unlocked-package-plugin.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=zzzzzzzz)
[![Install Unlocked Package Plugin](./content/btn-install-unlocked-package-plugin-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=zzzzzzzz)

---

## What's Included

This plugin includes some add-on metadata for Logger to support the Slack integration

1. Plugin configuration details stored in Logger's CMDT objects `LoggerPlugin__mdt.LogRetentionRules`
2. New custom metadata types
- `LogRetentionRule__mdt` - Used to configure rules that set the value of `Log__c`.`LogRetentionDate__c`. Each rules consists of 1 or more conditions, stored in `LogRetentionRuleCondition__mdt`.
- `LogRetentionRuleCondition__mdt` - Used to configure field-level conditions for retention rules - each condition checks a `LogEntry__c` or `Log__c` field for a specific value, regular expression (regex), or field comparisons.
3. Apex class `LogRetentionRulesPlugin` (and corresponding tests in `LogRetentionRulesPlugin_Tests`). The plugin class reads the configured rules & conditions and automatically sets the field `Log__c.LogRetentionDate__c` when new `Log__c` records are inserted.

---

## Installation Steps

After installing the unlocked package Nebula Logger v4.6.12 (or newer), you can then install the Log Retention Rules plugin unlocked package in a sandbox.

This plugin is currently in beta and cannot be installed in production - however, if you want to use it in your production org, you can deploy the metadata to your org using your preferred deployment tool (changes sets, sfdx, the metadata API, etc.).

### Quick Start: Adding Log Retention Rules and Conditions

After installing the plugin, you can add rules using the custom metadata types `LogRetentionRule__mdt` and `LogRetentionRuleCondition__mdt`. Multiple rules can be configured in your orgs, as shown below:

![Log Retention Rules plugin: Example Rule](./content/example-log-retention-rules-list-view.png)

In this example rule, for every inserted `LogEntry__c` record, 2 conditions are checked:

1. Does the entry's parent `Log__c` record have 1 or more error? (based on the roll-up summary field `Log__c.TotalERRORLogEntries__c`)
2. Does the entry's parent `Log__c` record have the scenario 'feature B'? (based on the text field `Log__c.Scenario__c`)

If any `LogEntry__c` record is inserted that meets these 2 conditions, then the parent `Log__c` record will have the field `LogRetentionDate__c` set to `System.today() + 30`.

![Log Retention Rules plugin: Example Rule](./content/example-log-retention-rule-with-conditions.png)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//-----------------------------------------------------------------------------------------------------------//
// This file is part of the Nebula Logger project, released under the MIT License. //
// The core functionality of this plugin's code originated in https://github.com/jongpie/ApexValidationRules //
// See LICENSE file or go to https://github.com/jongpie/NebulaLogger for full license details. //
//-----------------------------------------------------------------------------------------------------------//

// TODO revise suppressed PMD rules/clean up code
@SuppressWarnings(
'PMD.ApexDoc, PMD.AvoidDebugStatements, PMD.ApexAssertionsShouldIncludeMessage, PMD.CyclomaticComplexity, PMD.ExcessiveParameterList, PMD.MethodNamingConventions'
)
@IsTest
private class LogRetentionRulesPlugin_Tests {
@IsTest
static void it_should_set_retention_date_for_rule_with_one_condition() {
enablePlugin();
Date originalLogRetentionDate = System.today().addDays(Integer.valueOf(Logger.getUserSettings().DefaultNumberOfDaysToRetainLogs__c));
Integer numberOfDaysToRetainLogs = 90;
Date expectedLogRetentionDate = System.today().addDays(numberOfDaysToRetainLogs);
String scenario = 'Some scenario';
LogRetentionRule__mdt rule = createMockRule('rule_with_multiple_AND_conditions', numberOfDaysToRetainLogs);
rule.NumberOfDaysToRetainLogs__c = numberOfDaysToRetainLogs;
LogRetentionRulesPlugin.setMockRetentionRule(rule);
List<LogRetentionRuleCondition__mdt> conditions = new List<LogRetentionRuleCondition__mdt>{
createMockRuleCondition('Log__r.Scenario__c', 'EQUAL_TO', 'Value', scenario)
};
LogRetentionRulesPlugin.setMockRetentionRuleConditions(rule, conditions);

Log__c log = new Log__c(Scenario__c = scenario, TransactionId__c = '1234');
insert log;
log = [SELECT Id, LogRetentionDate__c FROM Log__c WHERE Id = :log.Id];
System.assertEquals(originalLogRetentionDate, log.LogRetentionDate__c);

LogEntry__c logEntry = new LogEntry__c(Log__c = log.Id, LoggingLevel__c = LoggingLevel.ERROR.name(), TransactionEntryNumber__c = 1);
insert logEntry;
log = [SELECT Id, LogRetentionDate__c FROM Log__c WHERE Id = :log.Id];
System.assertEquals(expectedLogRetentionDate, log.LogRetentionDate__c);
}

@IsTest
static void it_should_set_retention_date_for_rule_with_multiple_and_conditions() {
enablePlugin();
Date originalLogRetentionDate = System.today().addDays(Integer.valueOf(Logger.getUserSettings().DefaultNumberOfDaysToRetainLogs__c));
Integer numberOfDaysToRetainLogs = 90;
Date expectedLogRetentionDate = System.today().addDays(numberOfDaysToRetainLogs);
String scenario = 'Some scenario';
Integer numberOfERRORLogEntries = 1;
LogRetentionRule__mdt rule = createMockRule('rule_with_multiple_AND_conditions', numberOfDaysToRetainLogs);
LogRetentionRulesPlugin.setMockRetentionRule(rule);
List<LogRetentionRuleCondition__mdt> conditions = new List<LogRetentionRuleCondition__mdt>{
createMockRuleCondition('Log__r.Scenario__c', 'EQUAL_TO', 'Value', scenario),
createMockRuleCondition('Log__r.TotalERRORLogEntries__c', 'GREATER_THAN_OR_EQUAL_TO', 'Value', numberOfERRORLogEntries)
};
LogRetentionRulesPlugin.setMockRetentionRuleConditions(rule, conditions);

Log__c log = new Log__c(Scenario__c = scenario, TransactionId__c = '1234');
insert log;
log = [SELECT Id, LogRetentionDate__c FROM Log__c WHERE Id = :log.Id];
System.assertEquals(originalLogRetentionDate, log.LogRetentionDate__c);

LogEntry__c logEntry = new LogEntry__c(Log__c = log.Id, LoggingLevel__c = LoggingLevel.ERROR.name(), TransactionEntryNumber__c = 1);
insert logEntry;
log = [SELECT Id, LogRetentionDate__c, Scenario__c, TotalERRORLogEntries__c FROM Log__c WHERE Id = :log.Id];
System.assertEquals(expectedLogRetentionDate, log.LogRetentionDate__c, log);
}

// @IsTest
// static void validateForRuleWithOrConditions() {
// String accountName1 = 'Some account';
// String accountName2 = 'another account';
// Account account = new Account(Name = 'Test account');
// account.Name = accountName1;

// LogRetentionRule__mdt rule = createMockRule();
// rule.ConditionLogicType__c = 'OR';
// List<LogRetentionRuleCondition__mdt> conditions = new List<LogRetentionRuleCondition__mdt>{
// createMockRuleCondition('Name', 'EQUAL_TO', 'Value', accountName1),
// createMockRuleCondition('Name', 'EQUAL_TO', 'Value', accountName2)
// };

// RecordValidator validator = new RecordValidator(account).setRule(rule, conditions);
// List<RecordValidator.ValidationRuleResult> results = validator.validate(false);
// System.assertEquals(1, results.size(), 'Expected 1 validation rule result');

// RecordValidator.ValidationRuleResult result = results.get(0);
// System.assertEquals(true, result.hasError, result);
// System.assertEquals(rule.ErrorMessage__c, result.errorMessage, result);

// try {
// validator.validate();
// System.assert(false, 'Exception expected on line above');
// } catch (RecordValidator.RecordValidatorException ex) {
// System.assert(ex.getMessage().contains(rule.ErrorMessage__c), ex);
// }
// }

// @IsTest
// static void validateForRuleWithCustomConditions() {
// String accountName1 = 'Some account';
// String accountName2 = 'another account';
// Integer accountAnnualRevenue = 123000;
// Account account = new Account(Name = 'Test account');
// account.Name = accountName1;
// account.AnnualRevenue = accountAnnualRevenue;

// LogRetentionRule__mdt rule = createMockRule();
// rule.ConditionLogicType__c = 'Custom';
// rule.CustomConditionLogic__c = '((1 OR 2) AND 3)';
// List<LogRetentionRuleCondition__mdt> conditions = new List<LogRetentionRuleCondition__mdt>{
// createMockRuleCondition('Name', 'EQUAL_TO', 'Value', accountName1),
// createMockRuleCondition('Name', 'EQUAL_TO', 'Value', accountName2),
// createMockRuleCondition('AnnualRevenue', 'GREATER_THAN_OR_EQUAL_TO', 'Value', accountAnnualRevenue)
// };

// RecordValidator validator = new RecordValidator(account).setRule(rule, conditions);
// List<RecordValidator.ValidationRuleResult> results = validator.validate(false);
// System.assertEquals(1, results.size(), 'Expected 1 validation rule result');

// RecordValidator.ValidationRuleResult result = results.get(0);
// System.assertEquals(true, result.hasError, result);
// System.assertEquals(rule.ErrorMessage__c, result.errorMessage, result);

// try {
// validator.validate();
// System.assert(false, 'Exception expected on line above');
// } catch (RecordValidator.RecordValidatorException ex) {
// System.assert(ex.getMessage().contains(rule.ErrorMessage__c), ex);
// }
// }

static void enablePlugin() {
// Set the plugin's parameters
LoggerPlugin__mdt slackPluginConfig = new LoggerPlugin__mdt(
IsEnabled__c = true,
PluginApiName__c = LogRetentionRulesPlugin.class.getName(),
PluginType__c = 'Apex'
);
LoggerSObjectHandler.setMockPlugin(Schema.LogEntry__c.SObjectType, slackPluginConfig);
}

static LogRetentionRule__mdt createMockRule(String developerName, Integer numberOfDaysToRetainLogs) {
return new LogRetentionRule__mdt(
ConditionLogicType__c = 'AND',
CustomConditionLogic__c = null,
DeveloperName = developerName,
IsEnabled__c = true,
NumberOfDaysToRetainLogs__c = numberOfDaysToRetainLogs
);
}

static LogRetentionRuleCondition__mdt createMockRuleCondition(String fieldPath, String operator, String valueType, Object value) {
String valueString = value instanceof String ? (String) value : JSON.serialize(value);
return new LogRetentionRuleCondition__mdt(
FieldPath__c = fieldPath,
Operator__c = operator,
SortOrder__c = null,
Value__c = valueString,
ValueType__c = valueType
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?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>Log Retention Rules</label>
<protected>false</protected>
<values>
<field>Description__c</field>
<value
xsi:type="xsd:string"
>Adds the ability to create &amp; deploy advanced, configurable rules for setting the retention date of Log__c records, using custom metadata types LogRetentionRule__mdt and LogRetentionRuleCondition__mdt.</value>
</values>
<values>
<field>PluginApiName__c</field>
<value xsi:type="xsd:string">LogRetentionRulesPlugin</value>
</values>
<values>
<field>PluginType__c</field>
<value xsi:type="xsd:string">Apex</value>
</values>
<values>
<field>SObjectType__c</field>
<value xsi:type="xsd:string">LogEntry__c</value>
</values>
</CustomMetadata>
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Layout xmlns="http://soap.sforce.com/2006/04/metadata">
<layoutSections>
<customLabel>false</customLabel>
<detailHeading>true</detailHeading>
<editHeading>true</editHeading>
<label>Information</label>
<layoutColumns>
<layoutItems>
<behavior>Required</behavior>
<field>MasterLabel</field>
</layoutItems>
<layoutItems>
<behavior>Required</behavior>
<field>DeveloperName</field>
</layoutItems>
</layoutColumns>
<layoutColumns>
<layoutItems>
<behavior>Required</behavior>
<field>LogRetentionRule__c</field>
</layoutItems>
<layoutItems>
<behavior>Edit</behavior>
<field>SortOrder__c</field>
</layoutItems>
</layoutColumns>
<style>TwoColumnsTopToBottom</style>
</layoutSections>
<layoutSections>
<customLabel>true</customLabel>
<detailHeading>true</detailHeading>
<editHeading>true</editHeading>
<label>Condition Details</label>
<layoutColumns>
<layoutItems>
<behavior>Required</behavior>
<field>FieldPath__c</field>
</layoutItems>
<layoutItems>
<behavior>Required</behavior>
<field>Operator__c</field>
</layoutItems>
</layoutColumns>
<layoutColumns>
<layoutItems>
<behavior>Required</behavior>
<field>ValueType__c</field>
</layoutItems>
<layoutItems>
<behavior>Edit</behavior>
<field>Value__c</field>
</layoutItems>
</layoutColumns>
<style>TwoColumnsTopToBottom</style>
</layoutSections>
<layoutSections>
<customLabel>false</customLabel>
<detailHeading>true</detailHeading>
<editHeading>true</editHeading>
<label>System Information</label>
<layoutColumns>
<layoutItems>
<behavior>Edit</behavior>
<field>IsProtected</field>
</layoutItems>
<layoutItems>
<behavior>Readonly</behavior>
<field>CreatedById</field>
</layoutItems>
</layoutColumns>
<layoutColumns>
<layoutItems>
<behavior>Required</behavior>
<field>NamespacePrefix</field>
</layoutItems>
<layoutItems>
<behavior>Readonly</behavior>
<field>LastModifiedById</field>
</layoutItems>
</layoutColumns>
<style>TwoColumnsTopToBottom</style>
</layoutSections>
<layoutSections>
<customLabel>true</customLabel>
<detailHeading>true</detailHeading>
<editHeading>false</editHeading>
<label>Custom Links</label>
<layoutColumns />
<layoutColumns />
<layoutColumns />
<style>CustomLinks</style>
</layoutSections>
<showEmailCheckbox>false</showEmailCheckbox>
<showHighlightsPanel>false</showHighlightsPanel>
<showInteractionLogPanel>false</showInteractionLogPanel>
<showRunAssignmentRulesCheckbox>false</showRunAssignmentRulesCheckbox>
<showSubmitAndAttachButton>false</showSubmitAndAttachButton>
<summaryLayout>
<masterLabel>00h1F000005cUjP</masterLabel>
<sizeX>4</sizeX>
<sizeY>0</sizeY>
<summaryLayoutStyle>Default</summaryLayoutStyle>
</summaryLayout>
</Layout>
Loading

0 comments on commit 03802c1

Please sign in to comment.