Skip to content

Commit

Permalink
Optionally enforce scenario-based logging usage (#648)
Browse files Browse the repository at this point in the history
* Added the ability to optionally enforce using scenario-based logging. This is controlled by a new LoggerParameter__mdt record, RequireScenarioUsage, and is disabled by default.

* Upgraded LoggerScenarioRecordPage flexipage to use dynamic related lists, added missing field permission to LoggerAdmin

* Scope creep: added a shell script to handle generating log files for 'sf apex tail log'
  • Loading branch information
jongpie authored Mar 13, 2024
1 parent 11964ad commit f6b0afc
Show file tree
Hide file tree
Showing 15 changed files with 256 additions and 29 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.2
## Unlocked Package - v4.13.3

[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y000001MkEmQAK)
[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y000001MkEmQAK)
[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y000001MkEwQAK)
[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y000001MkEwQAK)
[![View Documentation](./images/btn-view-documentation.png)](https://jongpie.github.io/NebulaLogger/)

`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y000001MkEmQAK`
`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y000001MkEwQAK`

`sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y000001MkEmQAK`
`sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y000001MkEwQAK`

---

Expand Down
11 changes: 6 additions & 5 deletions config/linters/lint-staged.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ module.exports = {
return [`eslint --config ./config/linters/.eslintrc.json ${filenames.join(' ')} --fix`];
// FIXME this command should only run tests for the changed LWCs (instead of running tests for all LWCs)
// return [`eslint --config ./config/linters/.eslintrc.json ${filenames.join(' ')} --fix`, `npm run test:lwc`];
},
'*.{cls,trigger}': filenames => {
return filenames.map(
filename => `sf scanner run --pmdconfig ./config/linters/pmd-ruleset.xml --engine pmd --severity-threshold 3 --target '${filename}'`
);
// return [`npm run scan:apex`, `npm run docs:fix && git add ./docs/ && git commit --amend --no-edit`];
}
// FIXME this command should only scan the changed Apex files (instead of scanning all Apex files)
// '*.{cls,trigger}': () => {
// return [`npm run scan:apex`];
// // return [`npm run scan:apex`, `npm run docs:fix && git add ./docs/ && git commit --amend --no-edit`];
// }
};
4 changes: 4 additions & 0 deletions docs/apex/Configuration/LoggerParameter.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ Controls if Nebula Logger queries `Schema.User` data. When set to `false`, any `

Indicates if Nebula Logger queries `Schema.User` data is queried synchronously & populated on `LogEntryEvent__e` records. When set to `false`, any `Schema.User` fields on `LogEntryEvent__e` that rely on querying will not be populated - the data will instead be queried asynchronously and populated on any resulting `Log__c` records. Controlled by the custom metadata record `LoggerParameter.QueryUserDataSynchronously`, or `true` as the default

#### `REQUIRE_SCENARIO_USAGE``Boolean`

Indicates if Nebula Logger will enforce scenario-based logging to be used. When set to `false`, specifying a scenario is completely optional. When set to `true`, a scenario is required to be set before any logging can occur. If a logging method is called & the current scenario is null/blank, then Nebula Logger will throw a runtime exception. Controlled by the custom metadata record `LoggerParameter.RequireScenarioUsage`, or `false` as the default

#### `SEND_ERROR_EMAIL_NOTIFICATIONS``Boolean`

Indicates if Nebula Logger will send an error email notification if any internal exceptions occur. Controlled by the custom metadata record `LoggerParameter.SendErrorEmailNotifications`, or `true` as the default
Expand Down
4 changes: 4 additions & 0 deletions docs/apex/Logger-Engine/Logger.md
Original file line number Diff line number Diff line change
Expand Up @@ -5730,6 +5730,10 @@ The new entry's instance of `LogEntryEventBuilder`, useful for chaining met

### Inner Classes

#### Logger.LoggerException class

---

#### Logger.QueueableSaver class

Inner class for publishing log entries via the Queueable interface.
Expand Down
20 changes: 20 additions & 0 deletions nebula-logger/core/main/configuration/classes/LoggerParameter.cls
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,26 @@ public class LoggerParameter {
private set;
}

/**
* @description Indicates if Nebula Logger will enforce scenario-based logging to be used.
* When set to `false`, specifying a scenario is completely optional.
* When set to `true`, a scenario is required to be set before any logging can occur.
* If a logging method is called & the current scenario is null/blank, then Nebula Logger will throw a runtime exception.
* Controlled by the custom metadata record `LoggerParameter.RequireScenarioUsage`, or `false` as the default
*/
public static final Boolean REQUIRE_SCENARIO_USAGE {
get {
if (REQUIRE_SCENARIO_USAGE == null) {
// Most features in Nebula Logger are enabled (true) by default,
// but this one is intentionally set to false by default - not
// all orgs want or need to use scenario-based logging
REQUIRE_SCENARIO_USAGE = getBoolean('RequireScenarioUsage', false);
}
return REQUIRE_SCENARIO_USAGE;
}
private set;
}

/**
* @description Indicates if Nebula Logger will send an error email notification if any internal exceptions occur.
* Controlled by the custom metadata record `LoggerParameter.SendErrorEmailNotifications`, or `true` as the default
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>Require Scenario Usage</label>
<protected>false</protected>
<values>
<field>Description__c</field>
<value xsi:type="xsd:string">When set to &apos;false&apos; (default), specifying a scenario is completely optional.

When set to &apos;true&apos;, a scenario is required to be set before any logging can occur. If a logging method is called &amp; the current scenario is null/blank, then Nebula Logger will throw a runtime exception.</value>
</values>
<values>
<field>Value__c</field>
<value xsi:type="xsd:string">false</value>
</values>
</CustomMetadata>
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@
<flexiPageRegions>
<itemInstances>
<componentInstance>
<componentInstanceProperties>
<name>actionNames</name>
<valueList>
<valueListItems>
<value>Log__c.Manage</value>
</valueListItems>
</valueList>
</componentInstanceProperties>
<componentInstanceProperties>
<name>adminFilters</name>
</componentInstanceProperties>
<componentInstanceProperties>
<name>maxRecordsToDisplay</name>
<value>30</value>
</componentInstanceProperties>
<componentInstanceProperties>
<name>parentFieldApiName</name>
<value>LoggerScenario__c.Id</value>
Expand All @@ -42,19 +57,62 @@
<value>Logs__r</value>
</componentInstanceProperties>
<componentInstanceProperties>
<name>relatedListComponentOverride</name>
<name>relatedListDisplayType</name>
<value>ADVGRID</value>
</componentInstanceProperties>
<componentInstanceProperties>
<name>rowsToDisplay</name>
<value>30</value>
<name>relatedListFieldAliases</name>
<valueList>
<valueListItems>
<value>NAME</value>
</valueListItems>
<valueListItems>
<value>StartTime__c</value>
</valueListItems>
<valueListItems>
<value>LoggedByUsernameLink__c</value>
</valueListItems>
<valueListItems>
<value>TransactionId__c</value>
</valueListItems>
<valueListItems>
<value>TotalLogEntries__c</value>
</valueListItems>
<valueListItems>
<value>TotalERRORLogEntries__c</value>
</valueListItems>
<valueListItems>
<value>TotalWARNLogEntries__c</value>
</valueListItems>
<valueListItems>
<value>OWNER.ALIAS</value>
</valueListItems>
<valueListItems>
<value>Priority__c</value>
</valueListItems>
<valueListItems>
<value>Status__c</value>
</valueListItems>
</valueList>
</componentInstanceProperties>
<componentInstanceProperties>
<name>relatedListLabel</name>
<value>Logs</value>
</componentInstanceProperties>
<componentInstanceProperties>
<name>showActionBar</name>
<value>true</value>
</componentInstanceProperties>
<componentName>force:relatedListSingleContainer</componentName>
<identifier>force_relatedListSingleContainer</identifier>
<componentInstanceProperties>
<name>sortFieldAlias</name>
<value>StartTime__c</value>
</componentInstanceProperties>
<componentInstanceProperties>
<name>sortFieldOrder</name>
<value>Descending</value>
</componentInstanceProperties>
<componentName>lst:dynamicRelatedList</componentName>
<identifier>lst_dynamicRelatedList</identifier>
</componentInstance>
</itemInstances>
<name>relatedTabContent</name>
Expand All @@ -63,6 +121,18 @@
<flexiPageRegions>
<itemInstances>
<componentInstance>
<componentInstanceProperties>
<name>actionNames</name>
<valueList>
<valueListItems>
<value>MassChangeOwner</value>
</valueListItems>
</valueList>
</componentInstanceProperties>
<componentInstanceProperties>
<name>maxRecordsToDisplay</name>
<value>30</value>
</componentInstanceProperties>
<componentInstanceProperties>
<name>parentFieldApiName</name>
<value>LoggerScenario__c.Id</value>
Expand All @@ -72,19 +142,56 @@
<value>LogEntries__r</value>
</componentInstanceProperties>
<componentInstanceProperties>
<name>relatedListComponentOverride</name>
<name>relatedListDisplayType</name>
<value>ADVGRID</value>
</componentInstanceProperties>
<componentInstanceProperties>
<name>rowsToDisplay</name>
<value>30</value>
<name>relatedListFieldAliases</name>
<valueList>
<valueListItems>
<value>NAME</value>
</valueListItems>
<valueListItems>
<value>Timestamp__c</value>
</valueListItems>
<valueListItems>
<value>LoggedByUsernameLink__c</value>
</valueListItems>
<valueListItems>
<value>Origin__c</value>
</valueListItems>
<valueListItems>
<value>Message__c</value>
</valueListItems>
<valueListItems>
<value>LoggingLevelWithImage__c</value>
</valueListItems>
<valueListItems>
<value>Log__c</value>
</valueListItems>
<valueListItems>
<value>RecordLink__c</value>
</valueListItems>
</valueList>
</componentInstanceProperties>
<componentInstanceProperties>
<name>relatedListLabel</name>
<value>Log Entries</value>
</componentInstanceProperties>
<componentInstanceProperties>
<name>showActionBar</name>
<value>true</value>
<value>false</value>
</componentInstanceProperties>
<componentName>force:relatedListSingleContainer</componentName>
<identifier>force_relatedListSingleContainer3</identifier>
<componentInstanceProperties>
<name>sortFieldAlias</name>
<value>Timestamp__c</value>
</componentInstanceProperties>
<componentInstanceProperties>
<name>sortFieldOrder</name>
<value>Descending</value>
</componentInstanceProperties>
<componentName>lst:dynamicRelatedList</componentName>
<identifier>lst_dynamicRelatedList2</identifier>
</componentInstance>
</itemInstances>
<name>Facet-bf77ace2-6cee-4ca4-88b4-990633a74278</name>
Expand Down Expand Up @@ -125,7 +232,7 @@
<componentInstance>
<componentInstanceProperties>
<name>active</name>
<value>true</value>
<value>false</value>
</componentInstanceProperties>
<componentInstanceProperties>
<name>body</name>
Expand All @@ -141,6 +248,10 @@
</itemInstances>
<itemInstances>
<componentInstance>
<componentInstanceProperties>
<name>active</name>
<value>false</value>
</componentInstanceProperties>
<componentInstanceProperties>
<name>body</name>
<value>Facet-bf77ace2-6cee-4ca4-88b4-990633a74278</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1558,6 +1558,11 @@
<field>Log__c.TransactionScenarioText__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Log__c.TransactionScenario__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Log__c.UserLicenseDefinitionKey__c</field>
Expand Down
12 changes: 11 additions & 1 deletion nebula-logger/core/main/logger-engine/classes/Logger.cls
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
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.13.2';
private static final String CURRENT_VERSION_NUMBER = 'v4.13.3';
private static final System.LoggingLevel FALLBACK_LOGGING_LEVEL = System.LoggingLevel.DEBUG;
private static final List<LogEntryEventBuilder> LOG_ENTRIES_BUFFER = new List<LogEntryEventBuilder>();
private static final String MISSING_SCENARIO_ERROR_MESSAGE = 'No logger scenario specified. A scenario is required for logging in this org.';
private static final String ORGANIZATION_DOMAIN_URL = System.URL.getOrgDomainUrl()?.toExternalForm();
private static final String REQUEST_ID = System.Request.getCurrent().getRequestId();
private static final Map<String, SaveMethod> SAVE_METHOD_NAME_TO_SAVE_METHOD = new Map<String, SaveMethod>();
Expand Down Expand Up @@ -3351,6 +3352,10 @@ global with sharing class Logger {
private static LogEntryEventBuilder newEntry(System.LoggingLevel loggingLevel, Boolean shouldSave) {
LogEntryEventBuilder logEntryEventBuilder = new LogEntryEventBuilder(getUserSettings(), loggingLevel, shouldSave);
if (logEntryEventBuilder.shouldSave()) {
if (LoggerParameter.REQUIRE_SCENARIO_USAGE && String.isBlank(currentEntryScenario)) {
throw new LoggerException(MISSING_SCENARIO_ERROR_MESSAGE);
}

LogEntryEvent__e logEntryEvent = logEntryEventBuilder.getLogEntryEvent();
logEntryEvent.ApiVersion__c = getOrganizationApiVersion();
logEntryEvent.EntryScenario__c = currentEntryScenario;
Expand Down Expand Up @@ -3493,6 +3498,11 @@ global with sharing class Logger {
}
}

// Inner class used for any exceptions specific to Nebula Logger functionality
@SuppressWarnings('PMD.ApexDoc')
public class LoggerException extends Exception {
}

// Inner class used for deserializing data from the status API
@SuppressWarnings('PMD.ApexDoc, PMD.VariableNamingConventions')
public class StatusApiResponse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//------------------------------------------------------------------------------------------------//
import FORM_FACTOR from '@salesforce/client/formFactor';

const CURRENT_VERSION_NUMBER = 'v4.13.2';
const CURRENT_VERSION_NUMBER = 'v4.13.3';

// JavaScript equivalent to the Apex class ComponentLogger.ComponentLogEntry
const ComponentLogEntry = class {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,33 @@ private class LoggerParameter_Tests {
System.Assert.areEqual(mockValue, returnedValue);
}

@IsTest
static void it_should_use_false_as_default_constant_value_for_require_scenario_usage_when_null() {
LoggerParameter.setMock(new LoggerParameter__mdt(DeveloperName = 'RequireScenarioUsage', Value__c = null));

Boolean returnedValue = LoggerParameter.REQUIRE_SCENARIO_USAGE;

System.Assert.isFalse(returnedValue);
}

@IsTest
static void it_should_return_constant_value_for_require_scenario_usage_when_true() {
LoggerParameter.setMock(new LoggerParameter__mdt(DeveloperName = 'RequireScenarioUsage', Value__c = JSON.serialize(true)));

Boolean returnedValue = LoggerParameter.REQUIRE_SCENARIO_USAGE;

System.Assert.isTrue(returnedValue);
}

@IsTest
static void it_should_return_constant_value_for_require_scenario_usage_when_false() {
LoggerParameter.setMock(new LoggerParameter__mdt(DeveloperName = 'RequireScenarioUsage', Value__c = JSON.serialize(false)));

Boolean returnedValue = LoggerParameter.REQUIRE_SCENARIO_USAGE;

System.Assert.isFalse(returnedValue);
}

@IsTest
static void it_should_return_constant_value_for_send_error_email_notifications() {
Boolean mockValue = false;
Expand Down
Loading

0 comments on commit f6b0afc

Please sign in to comment.