Skip to content
Wim edited this page Oct 11, 2021 · 3 revisions

Logger

The logger offers a generic solution to have one entry point in apex coding for adding log messages. The actual endpoint of where the messages will be going is dynamic. By default all the messages will be send to the System Debug log, but this logic can be extended or replaced entirely.

The log entries can optionally be filtered based on the log level, e.g. Error, Info, Debug. to allow a more granular logging when needed.

Design Solutions

The solution is divided into three elements, the logger, log appender and the log formatter, to offer the highest level of flexibility and configurability.

Logger

An implementation of the fflib_ILogger interface will handle every single log entry. Throughout the apex code this implementation can be referenced to add INFO, DEBUG, WARNING and ERROR entries to the log.

Log Message Formatter

The Log Message Formatter will format the log entry into a readable string. Any implementation from the interface fflib_LogMessageFormatter can be utilized to customize the default behavior. Use the setMessageFormatter(fflib_ILogMessageFormatter formatter) method to replace the default implementation.

Log Appender

To offer flexibility in the kind of endpoint that is actually being used to send the log entries to, the logic is separated via the interface fflib_ILogAppender. Any implementation of this interface will accept the formatted log messages and deliver it to the desired endpoint.

The System.Debug is used by default when there is no log appender set via the method setLogAppender(fflib_ILogAppender appender). The setLogAppender method replaces the default appender, while the addLogAppender method add another appender on top of the existing. Using addLogAppender will result into multiple log appenders allowing the option to log a single entry to multiple endpoints.

Log Appender Filter

The Log Appender filter is an abstract class that will act as an filter for an implementation of fflib_ILogAppender. All log entries will be filtered based on three criteria; 1. User defined log level 2. Organisation wide log level 3. Apex (run-time) log level

Whenever an log entry is appended and matches one of these criteria (greater than or equal), then the entry can be accepted by the appender by utilising the method Boolean isValidLogLevel(System.LoggingLevel logLevel).

Configuring the filter

The filter can be configured via a hierarchical custom setting named fflib_LogLevelFilterc. It only has one field named LogLevelc containing the log level values: - NONE - ERROR - WARN - INFO - DEBUG

The 'Default Organization Level Value' sets the default logging level for the entire Org. To debug a specific user or profile we can create a record and set the required level.

Another way of configuring the filter is by using the setLoggingLevel method, however this is primarily used while debugging an issue in realtime.

Log Appender Platform Events

A Log appender named fflib_LogEventAppender can be added to the logger in order to publish Salesforce Platform Events. The benefit of using these events is that they cannot be rolled back even though and unhandled-exception occurs and Salesforce does an automatic database rollback.

To enable the Platform Events just add another appender to the logger:

fflib_ILogAppender logAppender = new MyCustomLogAppender();
fflib_ILogger logger = new fflib_Logger().addAppender(logAppender);

Method reference

fflib_ILogger

void add(String message);

Adds a log entry of the type System.LoggingLevel.INFO with the provided message.

fflib_ILogger logger = new fflib_Logger();
logger.add('My error message);

void debug(Exception e);

Adds a log entry of the type System.LoggingLevel.DEBUG based on the input provided by the caught exception.

fflib_ILogger logger = new fflib_Logger();
try
{
    ...
}
catch (Exception e)
{
    logger.debug(e);
}

void debug(String message);

Adds a log entry with the provided message.

fflib_ILogger logger = new fflib_Logger();
logger.debug('My error message);

void debug(String message, List\<String\> args);

Adds a log entry of the type System.LoggingLevel.DEBUG with the provided message. The String.format method is utilised accepting the args to complete the message.

fflib_ILogger logger = new fflib_Logger();
String myVar = 'Test';
logger.debug('myVar = {0}', new List<String>{ myVar });

void error(Exception e);

Adds a log entry of the type System.LoggingLevel.DEBUG based on the input provided by the caught exception.

fflib_ILogger logger = new fflib_Logger();
try
{
    ...
}
catch (Exception e)
{
    logger.error(e);
}

void error(String message);

Adds a log entry of the type System.LoggingLevel.ERROR with the provided message.

fflib_ILogger logger = new fflib_Logger();
logger.debug('My error message);

void error(String message, List\<String\> args);

Adds a log entry of the type System.LoggingLevel.ERROR with the provided message. The String.format method is utilised accepting the args to complete the message.

fflib_ILogger logger = new fflib_Logger();
String myVar = 'Test';
logger.error('myVar = {0}', new List<String>{ myVar });

void info(String message);

Adds a log entry of the type System.LoggingLevel.INFO with the provided message.

fflib_ILogger logger = new fflib_Logger();
logger.info('My error message);

void info(String message, List\<String\> args);

Adds a log entry of the type System.LoggingLevel.INFO with the provided message. The String.format method is utilised accepting the args to complete the message.

fflib_ILogger logger = new fflib_Logger();
String myVar = 'Test';
logger.info('myVar = {0}', new List<String>{ myVar });

void warning(String message);

Adds a log entry of the type System.LoggingLevel.WARN with the provided message.

fflib_ILogger logger = new fflib_Logger();
logger.warning('My error message);

void warning(String message, List\<String\> args);

Adds a log entry of the type System.LoggingLevel.WARN with the provided message. The String.format method is utilised accepting the args to complete the message.

fflib_ILogger logger = new fflib_Logger();
String myVar = 'Test';
logger.warning('myVar = {0}', new List<String>{ myVar });

fflib_ILogger addAppender(fflib_ILogAppender appender);

Adds another appender, with a customized implementation of fflib_ILogAppender, to the logger allowing multiple endpoint to send log entries.

fflib_ILogger setAppender(fflib_ILogAppender appender);

Replaces the default appender with a customized implementation of fflib_ILogAppender.

fflib_ILogger setMessageFormatter(fflib_ILogMessageFormatter formatter);

Replaces the default log message formatter with a customized implementation of fflib_LogMessageFormatter.

fflib_LogMessageFormatter

The default virtual implementation of this interface is located at fflib_Logger.LogMessageFormatter

A custom implementation of fflib_LogMessageFormatter can be added to the logger utility via;

public class MyCustomMessageFormatter implements fflib_LogMessageFormatter
{
    ...
}
fflib_ILogger logger = new fflib_Logger();
logger.setMessageFormatter( new MyCustomMessageFormatter());
or
fflib_ILogger logger = new fflib_Logger().setMessageFormatter( new MyCustomMessageFormatter());

String format(String message);

This method provides the ability to customize the message before it is send to the Log Appender

Default implementation by fflib_Logger.LogMessageFormatter is to return the message string unchanged.

String format(String message, List\<String\> args);

This method provides the ability to customize the message before it is send to the Log Appender. The String.format method is utilised accepting the args to format the returned string.

Default implementation by fflib_Logger.LogMessageFormatter is to return the message using String.format.

String format(Exception e);

This method provides the ability to customize the message before it is send to the Log Appender. The String.format method is utilised accepting the args to format the returned string.

Default implementation by fflib_Logger.LogMessageFormatter is to return the to string converted exception, the Exception.getCause() and the Exception.getStrackTraceString().

fflib_ILogAppender

The default virtual implementation of this interface is located at fflib_Logger.SystemAppender, and is using the System.debug as endpoint to send the log entries.

A custom implementation of fflib_ILogAppender can be added to the logger utility via;

public class MyCustomLogAppender implements fflib_ILogAppender
{
    ...
}
fflib_ILogger logger = new fflib_Logger();
logger.setAppender( new MyCustomLogAppender());
or
fflib_ILogger logger = new fflib_Logger().setAppender( new MyCustomLogAppender());

void append(String message);

Adds a generic message of no specific type to the log endpoint

void append(System.LoggingLevel loggingLevel, String message);

Adds the given message of the given loggingLevel to the log endpoint.

fflib_ALogAppenderFilter

Constants

DEFAULT_LEVEL = System.LoggingLevel.ERROR;

Methods

setLoggingLevel(System.LoggingLevel logLevel)

Overrides the default Apex logging level. Particular useful while debugging a specific part of the execution context.

Signature

public void setLoggingLevel(System.LoggingLevel logLevel)

Usage
fflib_ILogAppender logAppender = new MyCustomLogAppender()
fflib_ILogger logger = new fflib_Logger().setAppender(logAppender);

// some logic

// Let activate a more granular logging level
logAppender.setLoggingLevel(System.LoggingLevel.DEBUG);

// ...
// The code I want to debug
// ...

// And return it back to the default
logAppender.setLoggingLevel(logAppender.DEFAULT_LEVEL);

// other logic

fflib_LogEventAppender

Usage
fflib_ILogAppender logEventAppender = new fflib_LogEventAppender()
fflib_ILogger logger = new fflib_Logger().addAppender(logEventAppender);

Example Implementation

Example implementation of the fflib_Logger

Application.cls Create preferably one single instance of the logger, the ideal location would be the Application class.

public with sharing class Application
{
    public static final fflib_ILogger Logger = new fflib_Logger()
                                    .addAppender(new fflib_LogEventAppender())
}

MyClass At any location in the apex code the logger can be referenced from the Application class.

public with sharing class MyClass
{
    public void myMethod()
    {
        Application.Logger.error('An Error Message');
    }
}

MyLogEventListener.cls When the fflib_LogEventAppender is used as log appender, then you can create event listeners.

public with sharing class MyLogEventListener extends fflib_LogEventDomain
{

    public MyLogEventListener(List<SObject> records)
    {
        super(records);
    }

    public run()
    {
        for(LogEntry logEntry : logEntries)
        {
            // Some logic to handle the log entries
            System.debug(logEntry.loggingLevel, logEntry.message);
        }
    }
}

fflib_LogEventTrigger.trigger Use only one event listener per trigger so that each listener runs in its own execution context.

trigger fflib_LogEventTrigger on fflib_LogEvent__e (after insert)
{
	new MyLogEventListener(Trigger.new).run();
}
Clone this wiki locally