Skip to content

Dynamically Call Nebula Logger

Jonathan Gillespie edited this page Sep 16, 2024 · 14 revisions

Overview

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 generic Object 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 a subset of Nebula Logger's core features.

  • Instances of the CallableLogger class 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>();

    Quick Start Example

    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);

Available Actions

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.

Returned Output for All Actions

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 key isSuccess, with a value of true
    • 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 key isSuccess, with a value of false. It also includes 3 String values for the exception that was thrown:
    1. exceptionMessage - the value of thrownException.getMessage()
    2. exceptionStackTrace - the value of thrownException.getStackTraceString()
    3. exceptionType - the value of thrownException.getTypeName()

Example Syntax for All Actions

// 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'));

newEntry Action

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, like Logger.error('hello, world');
  • warn() overloads, like Logger.warn('hello, world');
  • info() overloads, like Logger.info('hello, world');
  • debug() overloads, like Logger.debug('hello, world');
  • fine() overloads, like Logger.fine('hello, world');
  • finer() overloads, like Logger.fine('rhello, world');
  • finest() overloads, like Logger.finest('hello, world');
  • newEntry() overloads, like Logger.newEntry(LoggingLevel.INFO, 'hello, world');

Input Values

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.

Output Values

No additional output values are returned for this action.

Example Usage
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);

saveLog Action

This action is used to save any pending new log entries in Nebula Logger. It is the equivalent to using Logger.saveLog()

Input Values

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.

Output Values

No additional output values are returned for this action.

Example Usage

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' });

getTransactionId Action

This action is used to return Nebula Logger's unique identifier for the current transaction. It is the equivalent to using Logger.getTransactionId()

Input Values

No input values are used for this action.

Output Values
Output Map Key Datatype
transactionId String

Example Usage

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'));

getParentLogTransactionId Action

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()

Input Values

No input values are used for this action.

Output Values

Output Map Key Datatype
parentLogTransactionId String

Example Usage

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'));

setParentLogTransactionId Action

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 Values

Input Map Key Required Expected Datatype Notes
parentLogTransactionId Required String

Output Values

No output values are used for this action.

Example Usage

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'));

getScenario Action

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().

Input Values

No input values are used for this action.

Output Values

Output Map Key Datatype
scenario String

Example Usage

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'));

setScenario Action

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 Values

Input Map Key Required Expected Datatype Notes
scenario Required String

Output Values

No additional output values are used for this action.

Example Usage

Callable nebulaLoggerInstance = (Callable) Type.forName('CallableLogger')?.newInstance();

Map<String, Object> input = new Map<String, Object>{ 'scenario' => 'some scenario' };
nebulaLoggerInstance?.call('setScenario', input);

endScenario Action

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 Values

Input Map Key Required Expected Datatype Notes
scenario Required String

Output Values

No additional output values are used for this action.

Example Usage

Callable nebulaLoggerInstance = (Callable) Type.forName('CallableLogger')?.newInstance();

Map<String, Object> input = new Map<String, Object>{ 'scenario' => 'some scenario' };
nebulaLoggerInstance?.call('endScenario', input);

Clone this wiki locally