This is a framework adding persistent, configurable, and queryable log functionality to Salesforce.
Log data to Salesforce database for convenient access with reports and SOQL.
By using platform events, logs do not get rolled back when unhandled errors occur.
By using platform events, logs are written in a separate context with its own set of limits.
Configure data that should be filtered out of logs, e.g. Social Security numbers.
Configure which entries are logged by LoggingLevel/User
You can write Logs from Flows that use the features included in the framework.
A Lightning Web Component sees events in real times.
On the App Log view page, there is section to see current counts of records to help with purging strategies.
Provides both static methods, and a concrete class that implements an interface developers that might want to mock logging in tests, or provide other loggers.
Added "Invocable" properties to enable more advanced logging in Flows.
Added Permission Set for reading logs.
Added App page with LWC so logs can be viewed in realtime.
Added List View page which includes and LWC to view number of records by logging level.
Changed message filters so they are applied before creating event.
LogService.debug('my debug text', 'className');
LogService.debug('my debug text', 'className', affectedId);
LogService.warn('my warning text', 'className');
LogService.warn('my warning text', 'className', affectedId);
LogService.error('my error text', 'className');
LogService.error('my error text', 'className', affectedId);
LogService.error(myException, 'className');
LogService.error(myException, 'className', affectedId);
Exception example:
try{
Account acct = [SELECT Id FROM Account WHERE Id=:id];
} catch( Exception ex ){
LogService.error( ex, 'className');
}
To log from within a Flow, add an Immediate Action; select the type Apex; for Apex Class, select "Write Log"; then under Apex Variables, select the field "messages", then add the desired text.
@TestVIsible
ILogger log = new Logger(); // this allows you to switch to any ILogger for testing
log.debug('my debug text', 'className');
log.debug('my debug text', 'className', affectedId);
log.info('my info text', 'className');
log.warn('my warning text', 'className');
log.warn('my warning text', 'className', affectedId);
log.error('my error text', 'className');
log.error('my error text', 'className', affectedId);
log.error(myException, 'className');
log.error(myException, 'className', affectedId);
Manage records in the custom metadata type, Log Filter. Supply a value for Regex To Find and a value for Regex To Replace With.
- Set Regex To Find to "\d{3}-\d{2}-\d{4}". This looks for a basic US Social Security number.
- Set Regex To Replace With to "xxx-xx-xxxx".
This will replace the SSNs found above with the static text "xxx-xx-xxxx" in all log Messages and Short Messages.
- Set Regex To Find to "\d{3}-\d{2}-(\d{4})". This looks for a basic US Social Security number.
- Set Regex To Replace With to "xxx-xx-$1". $1 signifies the first capture group (the Regex To Find part in parens).
By using capture groups, this will obfuscate all but the last four digits of the SSNs found above in all log Messages and Short Messages. E.g. 123-45-6789 becomes xxx-xx-6789
The developer should be able to filter out anything that can be identified by a regex on an org-wide basis assuming it is supported in Apex.
You can easily test the end result in an anonymous Apex window by calling:
System.debug(LogService.filterString('My string'));
Replace 'My string' with whatever makes sense for your test.
For developers, to test before creating the Log Filter record, you might use something like this:
String sourceText = 'Hide the 123-12-1234 SSN.';
String regexToFind = '(\\d{3})-(\\d{2})-(\\d{4})';
String regexToReplaceWith = 'xxx-xx-$5';
String result = sourceText.replaceAll(regexToFind, regexToReplaceWith);
System.debug(result);
In code, we use "\d" to indicate a decimal. The first slash is unescaping the second slash for Apex string literals.
In the custom metadata, we should enter "\d" since it is not an Apex string literal.
Adding large numbers of filters can slow things down since each filter must be processed synchronously.
Though it's not expected, an obscure filter could break existing unit tests since Apex test "see" custom metadata, even without setting SeeAllData = true.
Using the Custom Metadata type Log Record Filter allows you to setup metadata records that determine what records should be logged per user. For instance, you could only log errors and warnings in production, but log Info in sandboxes. You could also communicate to log DEBUG records for a single user (like a developer).
This is intended to help reduce "noise" in the logs and can also be used to im prove performance and/or reduce Events fired should limits be an issue.
Use an asterisk (*) in the Active Log User field to indicate all users.
To indicate what levels should be logged, set the Log Levels CSV field. E.g. 'DEBUG,WARN,ERROR'
For the LoggingLevels, all fields should be capitalized. We could adapt the code, but it is more efficient to do it only when the metadata record is created.
IF you install this project, you will have a LogRecordFilter record that sets up * (all users) to log INFO,DEBUG,WARN,ERROR. It might be desirable to reduce this for production.
-
You can clone the repo, then use ant or your favorite IDE to deploy.
-
You might consider creating an unlocked package and deploying it.
-
Or use this handy link:
After deploying:
- Ensure users that should be able to read the logs are assigned the AppLogReader permission set.
- Add an assignment on the App Logs application to the profiles of users that should read logs.
- Ensure that the Profiles for users reading logs can view the following tabs: App Logs, Log Reader, Log List, Log Tester. These can be set to Default On.
Date | Features |
---|---|
2018-11-18 | Initial commit |
2019-02-18 | Added Logger instance class, and ILogger interface, allowing more versatility in object instantiation, and inheritance. |
2020-03-12 | Added Log filtering to help avoid logging sensitive data. Added a short Message field that is a truncated version of the message field to enable better SOQL manipulation. |
2020-03-15 | Added the ability to only log records configured in Log Record Filter custom metadata type. |
2020-03-15 | Added calls for LogService.info(). Added an invocable call to LogService.info(). |
2020-07-04 | Code clean up. Changed so message filter runs before the event is published to prevent publishing sensitive data. |
2023-05-16 | Fixed a deployment error. Added a permission set, Apex Log Reader, for reading log data. Split ClassMethod field into two field, Class__c and Method__c. |
2023-05-17 | Changed to DX Source. |
2023-07-24 | Refactored so most logic is in the Logger instance class |
Add a mechanism to optionally allow all logging of ERRORs to throw an exception when in test mode.
Add a mechanism to delete old logs systematically thru a scheduled job.
Develop a strategy to allow for multiple loggers (all implementing the ILogger interface).
Basic MIT license.
This does not replace built-in Salesforce Debug Logging. Built-in system debug logs are a good solution for debugging issues where the reproduction steps are known, and can be generated at will and read within a maximum 24-hours time-frame. Offline debugging uses the standard Salesforce logs.
To work with the library in a scratch org, here are a few tips.
To create a scratch org:
sfdx org create scratch -f ./config/project-scratch-def.json -a logger -v <dev hub>
To deploy source to scratch org:
sfdx project deploy start -o logger
To retrieve changes from scratch org:
sfdx project retrieve start -o logger
To delete scratch org:
sfdx org delete scratch -o logger