-
-
Notifications
You must be signed in to change notification settings - Fork 163
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
Callable
interface only has 1 method:Object call(String action, Map<String,Object> args)
. It leverages string keys and genericObject
values as a mechanism to provide loose coupling on Apex classes that may or may not exist in a Salesforce org. -
The
CallableLogger
class provides dynamic access to Nebula Logger's core features. -
Instances of the
CallableLogger
class be dynamically instantiated using something like this approach:// Check if some version of Nebula Logger is available in the org Type nebulaLoggerCallableType = Type.forName('Nebula', 'CallableLogger') ?? Type.forName('CallableLogger'); Callable nebulaLoggerCallable = (Callable) nebulaLoggerCallableType?.newInstance(); if (nebulaLoggerCallable == null) { // If it's null, then Nebula Logger isn't available in the org 🥲, // so we'll return early & skip logging in this example. You could also take // some other approach here, like use a fallback logging tool, // or call System.debug() instead, etc. return; } // If we made it here, then some version of Nebula Logger is available 🥳 // So, now we know we can use it to log nebulaLoggerCallable.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.
// Dynamically create a instance Nebula Logger's Callable Apex class (if it's available) Type nebulaLoggerCallableType = Type.forName('Nebula', 'CallableLogger') ?? Type.forName('CallableLogger'); Callable nebulaLoggerCallable = (Callable) nebulaLoggerCallableType?.newInstance(); if (nebulaLoggerCallable == null) { return; } // Example action: Add a basic "hello, world!" INFO entry Map<String, Object> infoEntryInput = new Map<String, Object>{ 'loggingLevel' => System.LoggingLevel.INFO, 'message' => 'hello, world!' }; nebulaLoggerCallable.call('newEntry', infoEntryInput); // Example action: Add an ERROR entry 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' }; nebulaLoggerCallable.call('newEntry', errorEntryInput); // Example: Save any pending log entries nebulaLoggerCallable.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.
The actions in CallableLogger
can also be used when logging in OmniStudio. As of Salesforce's Summer '21, OmniScripts and Integration Procedures support using Callable
Apex classes via remote actions. In Salesforce's docs, it states:
Vlocity Apex classes and the Remote Actions of OmniScripts and Integration Procedures support the Callable interface beginning in the Summer '21 release.
Although the VlocityOpenInterface and VlocityOpenInterface2 interfaces enable flexible implementations with a uniform signature, they reside in the Vlocity managed package and aren't truly Salesforce standards. However, classes that implement VlocityOpenInterface or VlocityOpenInterface2 also implement the Callable Interface, which is a Salesforce standard.
In addition, Remote Actions of OmniScripts and Integration Procedures can invoke any class that implements Callable, regardless of whether it implements VlocityOpenInterface or VlocityOpenInterface2. You specify the Remote Class, Remote Method, and Additional Input properties in the same way for classes that implement any of these interfaces.
This makes the CallableLogger
both the solution for dynamically logging in Apex, as well the solution to OmniStudio logging. Internally, the CallableLogger
class does some additional handling of OmniStudio's format for passing the args
parameter in call()
- but ultimately, the same actions & same inputs are available in both Apex & OmniStudio.
The Callable
interface's one method (shown below) expects an args
map as the second parameter. This gives a way for developers to dynamically pass any needed parameters/data to the action being called.
Object call(String action, Map<String,Object> args)
But not all actions in CallableLogger
require any input - and in those cases, you can simply pass null
for the args
parameter.
Example: newEntry
requires input, passed via the args
map parameter
// Dynamically create a instance Nebula Logger's Callable Apex class (if it's available)
Type nebulaLoggerCallableType = Type.forName('Nebula', 'CallableLogger') ?? Type.forName('CallableLogger');
Callable nebulaLoggerCallable = (Callable) nebulaLoggerCallableType?.newInstance();
if (nebulaLoggerCallable == null) {
return;
}
// Example: Add a basic "hello, world!" INFO entry
Map<String, Object> infoEntryInput = new Map<String, Object>{
'loggingLevel' => System.LoggingLevel.INFO,
'message' => 'hello, world!'
};
nebulaLoggerCallable.call('newEntry', infoEntryInput);
Example: saveLog
has some optional inputs, but no input is required. In this case, you can pass null
for args
, instead of needing to pass an empty (and pointless) instance of Map<String, Object>
// Dynamically create a instance Nebula Logger's Callable Apex class (if it's available)
Type nebulaLoggerCallableType = Type.forName('Nebula', 'CallableLogger') ?? Type.forName('CallableLogger');
Callable nebulaLoggerInstance = (Callable) nebulaLoggerCallableType?.newInstance();
if (nebulaLoggerInstance == null) {
return;
}
// Example: ✅ Save logs, using default settings (no input required)
nebulaLoggerInstance.call('saveLog', null);
// Example: ❌ When saving logs, passing an empty map to the call() method (shown below)
// is fine & everything will work, but it's not necessary for actions that have no required inputs
nebulaLoggerInstance.call('saveLog', new Map<String, Object>());
// Example: ℹ️ Save logs, using some optional settings
Map<String, Object> saveLogInput = new Map<String, Object>{
'saveMethodName' => 'SYNCHRONOUS_DML'
};
nebulaLoggerInstance.call('saveLog', saveLogInput);
Details about the specific inputs used for each action are documented below in the available actions section
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 3String
values for the exception that was thrown:-
exceptionMessage
- the value ofthrownException.getMessage()
-
exceptionStackTrace
- the value ofthrownException.getStackTraceString()
-
exceptionType
- the value ofthrownException.getTypeName()
-
As of release v4.14.16
, Nebula Logger automatically includes details about the current transaction in the output of all actions:
-
transactionId
: The current transaction ID, returned fromLogger.getTransactionId()
-
parentLogTransactionId
: The parent log transaction ID (ornull
if no parent has been set), returned fromLogger.getParentLogTransactionId()
-
requestId
: The Salesforce-generated request ID, returned fromSystem.Request.getCurrent().getRequestId()
Type nebulaLoggerCallableType = Type.forName('Nebula', 'CallableLogger') ?? Type.forName('CallableLogger');
Callable nebulaLoggerInstance = (Callable) nebulaLoggerCallableType?.newInstance();
if (nebulaLoggerInstance == null) {
return;
}
// An example of checking the result of the action call
Map<String, Object> output = (Map<String, Object>) nebulaLoggerCallable.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('hello, 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 |
parentLogTransactionId |
Optional | String |
When provided, Nebula Logger automatically uses the value to call Logger.setParentLogTransactionId(theValue); before creating the new entry. |
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.
User currentUser = [SELECT Id, Name FROM User WHERE Id = :UserInfo.getUserId()];
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' => currentUser
};
Callable nebulaLoggerCallable = (Callable) System.Type.forName('CallableLogger')?.newInstance();
nebulaLoggerCallable?.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 nebulaLoggerCallable = (System.Callable) System.Type.forName('CallableLogger')?.newInstance();
// Save using the default save method
nebulaLoggerCallable?.call('saveLog', null);
// Save using a specific save method
nebulaLoggerCallable?.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 nebulaLoggerCallable = (Callable) Type.forName('CallableLogger')?.newInstance();
Map<String, Object> output = (Map<String, Object>) nebulaLoggerCallable?.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 nebulaLoggerCallable = (Callable) Type.forName('CallableLogger')?.newInstance();
Map<String, Object> output = (Map<String, Object>) nebulaLoggerCallable?.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 nebulaLoggerCallable = (Callable) Type.forName('CallableLogger')?.newInstance();
Map<String, Object> output = (Map<String, Object>) nebulaLoggerCallable?.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 nebulaLoggerCallable = (Callable) Type.forName('CallableLogger')?.newInstance();
Map<String, Object> output = (Map<String, Object>) nebulaLoggerCallable?.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 nebulaLoggerCallable = (Callable) Type.forName('CallableLogger')?.newInstance();
Map<String, Object> input = new Map<String, Object>{ 'scenario' => 'some scenario' };
nebulaLoggerCallable?.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 nebulaLoggerCallable = (Callable) Type.forName('CallableLogger')?.newInstance();
Map<String, Object> input = new Map<String, Object>{ 'scenario' => 'some scenario' };
nebulaLoggerCallable?.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
Callable
Interface - 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