-
-
Notifications
You must be signed in to change notification settings - Fork 214
Dynamically Call Nebula Logger
As of v4.14.10, Nebula Logger includes the ability for ISVs & package developers to optionally leverage Nebula Logger for logging, when it's available in a customer's org. And when it's not available, your package can still be installed, and still be used.
This functionality is provided via the Apex class CallableLogger, which implements Apex's Callable interface.
-
The
Callableinterface only has 1 method:Object call(String action, Map<String,Object> args). It leverages string keys and genericObjectvalues as a mechanism to provide loose coupling on Apex classes that may or may not exist in a Salesforce org. -
The
CallableLoggerclass provides dynamic access to a subset of Nebula Logger's core features. -
Instances of the
CallableLoggerclass be dynamically instantiated using something like this approach:// Check for both the managed package (Nebula namespace) and the unlocked package to see if either is available Type nebulaLoggerType = Type.forName('Nebula', 'CallableLogger') ?? Type.forName('CallableLogger'); Callable nebulaLoggerInstance = (Callable) nebulaLoggerType?.newInstance(); if (nebulaLoggerInstance == null) { // If it's null, then neither of Nebula Logger's packages is available in the org 🥲 return; } // If we made it here, then some version of Nebula Logger is available (either the managed package or unlocked package) // So, now know we can use it to log nebulaLoggerInstance.call('someAction', new Map<String, Object>();
This sample Apex class adds 2 log entries & saves them, if Nebula Logger is available in the current org. If not, it exits early / nothing happens.
// Check for both the managed package ("Nebula" namespace) and the unlocked package to see if either is available Type nebulaLoggerType = Type.forName('Nebula', 'CallableLogger') ?? Type.forName('CallableLogger'); Callable nebulaLoggerInstance = (Callable) nebulaLoggerType?.newInstance(); if (nebulaLoggerInstance == null) { // If it's null, then neither of Nebula Logger's packages is available in the org 🥲 return; } // Example: Add a basic "hello, world!" INFO extry Map<String, Object> infoEntryInput = new Map<String, Object>{ 'loggingLevel' => System.LoggingLevel.INFO, 'message' => 'hello, world!' }; nebulaLoggerInstance.call('newEntry', infoEntryInput); // Example: Add an ERROR extry with an Apex exception Exception someException = new DmlException('oops'); Map<String, Object> errorEntryInput = new Map<String, Object>{ 'exception' => someException, 'loggingLevel' => LoggingLevel.ERROR, 'message' => 'An unexpected exception was thrown' }; nebulaLoggerInstance.call('newEntry', errorEntryInput); // Example: Save any pending log entries nebulaLoggerInstance.call('saveLog', null);
There are currently 8 actions supported by CallablerLogger - each action (discussed below) essentially corresponds to a similar method in the core Logger Apex class. These actions can also be used when logging in OmniStudio.
All actions return an instance of Map<String, Object> as the output. The call() method always returns an Object, so the returned value will have to be cast to Map<String, Object> if you wish to inspect the returned information.
- ✅When the
call()method finishes successfully, the map contains the keyisSuccess, with a value oftrue- Some actions will add additional values to the map. The specific output values for each action is documented below.
- ❌When the
call()method fails due to some catchable exception, the map contains the keyisSuccess, with a value offalse. It also includes 3Stringvalues for the exception that was thrown:-
exceptionMessage- the value ofthrownException.getMessage() -
exceptionStackTrace- the value ofthrownException.getStackTraceString() -
exceptionType- the value ofthrownException.getTypeName()
-
// An example of checking the result of the action call
Callable nebulaLoggerInstance = (Callable) Type.forName('CallableLogger')?.newInstance();
if (nebulaLoggerInstance == null) {
return;
}
Map<String, Object> output = (Map<String, Object>) nebulaLoggerInstance.call('saveLog', null);
System.debug('Log entries were successfully saved in Nebula Logger: ' + output.get('isSuccess'));
System.debug('Save exception type: ' + output.get('exceptionMessage'));
System.debug('Save exception message: ' + output.get('exceptionType'));
System.debug('Save exception stack trace: ' + output.get('exceptionStackTrace'));This action is used to add new log entries in Nebula Logger. It is the equivalent of using these methods (and their overloads) available in Logger:
-
error()overloads, likeLogger.error('hello, world'); -
warn()overloads, likeLogger.warn('hello, world'); -
info()overloads, likeLogger.info('hello, world'); -
debug()overloads, likeLogger.debug('hello, world'); -
fine()overloads, likeLogger.fine('hello, world'); -
finer()overloads, likeLogger.fine('rhello, world'); -
finest()overloads, likeLogger.finest('hello, world'); -
newEntry()overloads, likeLogger.newEntry(LoggingLevel.INFO, 'hello, world');
| Input Map Key | Required | Expected Datatype | Notes |
|---|---|---|---|
loggingLevel |
Required |
String or System.LoggingLevel
|
|
message |
Required | String |
|
exception |
Optional | System.Exception |
When provided, Nebula Logger automatically stores details about the provided exception the log entry to the specified SObject record ID. Cannot be used at the same time as record, recordList, or recordMap |
recordId |
Optional | Id |
When provided, Nebula Logger automatically ties the log entry to the specified SObject record ID. Cannot be used at the same time as record, recordList, or recordMap |
record |
Optional | SObject |
When provided, Nebula Logger automatically ties the log entry to the specified SObject record, and stores a JSON copy of the provided SObject record. Cannot be used at the same time as recordId, recordList, or recordMap |
recordList |
Optional | List |
When provided, Nebula Logger automatically stores a JSON copy of the provided list of SObject records. Cannot be used at the same time as recordId, record, or recordMap |
recordMap |
Optional | Map |
When provided, Nebula Logger automatically stores a JSON copy of the provided map of SObject records. Cannot be used at the same time as recordId, record, or recordList |
saveLog |
Optional | Boolean |
When set to true, Nebula Logger automatically saves any pending log entries. By default, log entries are not automatically saved. |
tags |
Optional | List |
When provided, Nebula Logger stores the provided strings as tags associated with the log entry. |
No additional output values are returned for this action.
Account someAccount = [SELECT Id, Name ]
Exception someException = new DmlException('oops');
// Add a new entry with an account & an exception as supporting context/data
Map<String, Object> newEntryInput = new Map<String, Object>{
'exception' => someException,
'loggingLevel' => LoggingLevel.ERROR,
'message' => 'An unexpected exception was thrown'
'record' => someAccount
};
Callable nebulaLoggerInstance = (Callable) System.Type.forName('CallableLogger')?.newInstance();
nebulaLoggerInstance?.call('newEntry', newEntryInput);This action is used to save any pending new log entries in Nebula Logger. It is the equivalent to using Logger.saveLog()
| Input Map Key | Required | Expected Datatype | Notes |
|---|---|---|---|
saveMethodName |
Optional | String |
When provided, the specified save method will be used by Nebula Logger to save any pending log entries. By default, log entries are saved using the save method configured in LoggerSettings__c.DefaultSaveMethod__c. |
No additional output values are returned for this action.
System.Callable nebulaLoggerInstance = (System.Callable) System.Type.forName('CallableLogger')?.newInstance();
// Save using the default save method
nebulaLoggerInstance?.call('saveLog', null);
// Save using a specific save method
nebulaLoggerInstance?.call('saveLog', new Map<String, Object>{ 'saveMethodName' => 'SYNCHRONOUS_DML' });This action is used to return Nebula Logger's unique identifier for the current transaction. It is the equivalent to using Logger.getTransactionId()
No input values are used for this action.
| Output Map Key | Datatype |
|---|---|
transactionId |
String |
Callable nebulaLoggerInstance = (Callable) Type.forName('CallableLogger')?.newInstance();
Map<String, Object> output = (Map<String, Object>) nebulaLoggerInstance?.call('getTransactionId', null);
System.debug('Current transaction ID: ' + (String) output.get('transactionId'));This action is used to return Nebula Logger's unique identifier for the parent log of the current transaction (if one has been set). It is the equivalent to using Logger.getParentLogTransactionId()
No input values are used for this action.
| Output Map Key | Datatype |
|---|---|
parentLogTransactionId |
String |
Callable nebulaLoggerInstance = (Callable) Type.forName('CallableLogger')?.newInstance();
Map<String, Object> output = (Map<String, Object>) nebulaLoggerInstance?.call('getParentLogTransactionId', null);
System.debug('Current parent log transaction ID: ' + (String) output.get('parentLogTransactionId'));This action is used to set Nebula Logger's the unique identifier for the parent log of the current transaction. It is the equivalent to using Logger.setParentLogTransactionId(String)
| Input Map Key | Required | Expected Datatype | Notes |
|---|---|---|---|
parentLogTransactionId |
Required | String |
No output values are used for this action.
Callable nebulaLoggerInstance = (Callable) Type.forName('CallableLogger')?.newInstance();
Map<String, Object> output = (Map<String, Object>) nebulaLoggerInstance?.call('getParentLogTransactionId', null);
System.debug('Current parent log transaction ID: ' + (String) output.get('parentLogTransactionId'));This action is used to return Nebula Logger's current scenario for scenario-based-logging (if one has been set). It is the equivalent to using Logger.getScenario().
No input values are used for this action.
| Output Map Key | Datatype |
|---|---|
scenario |
String |
Callable nebulaLoggerInstance = (Callable) Type.forName('CallableLogger')?.newInstance();
Map<String, Object> output = (Map<String, Object>) nebulaLoggerInstance?.call('getScenario', null);
System.debug('Current scenario: ' + (String) output.get('scenario'));This action is used to set Nebula Logger's current scenario for scenario-based-logging. It is the equivalent to using Logger.setScenario(String)
| Input Map Key | Required | Expected Datatype | Notes |
|---|---|---|---|
scenario |
Required | String |
No additional output values are used for this action.
Callable nebulaLoggerInstance = (Callable) Type.forName('CallableLogger')?.newInstance();
Map<String, Object> input = new Map<String, Object>{ 'scenario' => 'some scenario' };
nebulaLoggerInstance?.call('setScenario', input);This action is used to set Nebula Logger's current scenario for scenario-based-logging. It is the equivalent to using Logger.endScenario(String)
| Input Map Key | Required | Expected Datatype | Notes |
|---|---|---|---|
scenario |
Required | String |
No additional output values are used for this action.
Callable nebulaLoggerInstance = (Callable) Type.forName('CallableLogger')?.newInstance();
Map<String, Object> input = new Map<String, Object>{ 'scenario' => 'some scenario' };
nebulaLoggerInstance?.call('endScenario', input);- Assigning Permission Sets to Users
- Configuring Global Feature Flags
- Configuring Profile & User-Specific Settings
- Configuring Data Mask Rules
Manual Instrumentation
- Logging in Apex
- Logging in Flow & Process Builder
- Logging in Lightning Web Components & Aura Components
- Logging in OmniStudio
- Logging in OpenTelemetry (OTEL) REST API
ISVs & Package Dependencies
- Overview
- Optionally Use Nebula Logger (When Available) with
CallableInterface - Require Nebula Logger with Strongly-Coupled Package Dependency
Troubleshooting
Pub/Sub with Platform Events
Persisted Data with Custom Objects
- Logger Console app
- Assigning & Managing Logs
- Using 'View Related Log Entries' Component on Record Pages
- Deleting Old Logs
Official Plugins