Home Upgrade Search Memberlist Extras Hacker Tools Award Goals Help Wiki Follow Contact

HF Rulez the UniverseHF Rulez the Universe
Sigma
View all of my HELPFUL THREADS!!
Apex Logging Salesforce Guide Handler tutorial guidelines

Apex Logging and Exception Handler Guidelines

Posted 05-04-2021, 08:55 AM
Why do we create logs?

Creating a log is a very important function in any system. It keeps track of every event that happens in your system from the minute you start running it to the second you stop it and it will benefit groups of people like the following:
  • Developers -- will allow them to trace any code execution when finding bugs without enabling an Apex debugger and replicating the issue
  • Administrators -- will provide them a piece of detailed information that lets them know if the system is running correctly
  • Supports -- will provide them details and evidence of possible problems to help resolve customer issues.
When you write any code in Apex, you should ask yourself what you would want to see, from the point of view of the above three people. If you were monitoring a system, what would you want to be notified of? If you were handling a support case regarding some problem with the system, what information would you need? If you were tracing a bug in the code, what would you want to see logged?

Where should we log

Log as high up in call stack as possible, as close to the entry point into the code as possible.

Allow exceptions to bubble up to the execution context's entry point, and add logging there.

If it is necessary to add more context to the error, as it bubbles up, then we can catch it and wrap it in a new exception, then, throw that new exception. Like a Russian nesting doll in an elevator, going upwards in a tall building, stopping at some floors on the way up, and gaining a new layer at each stop.

If a certain step in the call chain decides to catch and examine the error, but take a different course of action because of that error happening, then, in that case, the error can be considered to have been handled, furthermore, since the code has been designed to deal with this, it seems unlikely that this is an exceptional case that should be logged.

E.g. Product Key Activation via Trigger and Rest API
Code
Order Trigger --> AutoApplyRenewalsUtility <-- (Saves the ErrorDetail on Order's field) ActivatableAccount
                  applyKeys(): void                                                     activateKey() : List<ErrorDetail>

API --> rest_ServiceAccountKey <-- (Saves the ErrorDetail on System Log) ActivatableAccount
        processRequest() : void                                          activateKey() : List<ErrorDetail>

Include helpful information when logging the _error_

When creating your log entries, always anticipate that there are emergency situations where the only thing you have is the log record, from which you have to understand what happened.
  • Put related object Ids to the log record so that we can easily identify the impact on the business.
  • Include a message from the exception and stack trace to the log record to help the developers where to debug the error.
  • Add context to the log to identify the type or nature of the log.
    • E.g. Log_Type__c = 'Lead Conversion Failed'
  • If possible, add remediation information to the log message or what was the purpose of the operation and its outcome.
  • Passwords and other sensitive information should not be included to the log record.

Avoid NULL values on the code
  • The overall goal is to avoid NullPointerException.
  • Don’t return NULL if there is a better alternative. For example, it’s way better to return new List() instead of NULL.
  • Always put NULL checks on the values you're using.

Logging record exceptions
  • No need to create a system log record if the nature of the error doesn't require a fix or the user was not affected and it did not prevent completing the process.
    • E.g. Blocking a duplicate Sync Log record on creation
  • Validation rule or missing required field errors and the user is able to see the error message via page/UI.

Sample of log types we can use
  • Batch Class Failed
  • Inbound Email Processing Failed
  • Lead Conversion Failed
  • Lead Generation Failed
  • Lead Closure Failed
  • License Transfer Failed
  • Change Registrant Failed
  • Opportunity Closure Failed
  • SendGrid Process Failed
  • SendGrid Response Failed
  • Compliance Check Launcher Failed
  • EDI Process Failed
  • Device Allocation Failed
  • Device Reregistration Failed
  • Device Restock Failed
  • Event Publish Failed
  • Product Key Activation Failed
  • Quote Service Failed
  • Webservice Process Failed

Source Class: const_SystemLog

Utilize DML exception logging using DMLObject class

DMLObject is an abstract class that should be extended by any domain-layer or service-layer class to manage DML statement execution and error handling in a transaction/unit of work. This can be used to provide consistency when making database changes and avoiding partial updates

https://github.com/apex-enterprise-patte...OfWork.cls
https://martinfowler.com/eaaCatalog/unitOfWork.html

The implemented UnitOfWork pattern has some advantages:
  • SavePoint encapsulation
  • object relationship and concurrency problems that come with aggregating DML operations
  • UnitOfWork can be passed between services via method overloading meaning there is a single instance in the transactional scope

Adding log using SystemLogService class

SystemLogService.cls
Code
public without sharing class SystemLogService extends DMLObject {
    /**
     * The maximum length of the Data field for the System Log object
     */
    private static integer dataFieldLength
    {
        get
        {
            if(dataFieldLength == null)
            {
                dataFieldLength = System_Log__c.Data__c.getDescribe().getLength();
            }
            return dataFieldLength;
        }
        set;
    }

    private static SystemLogService instance { get; set; }
    
    private SystemLogService(){
        this.errorDest = ErrorDestination.IGNORE;
    }

    /**
     * Get singleton instance of the SystemLogService to use DMLObject
     * for inserting system log records
     */
    private static SystemLogService getInstance() {
        if (instance == null) {
            instance = new SystemLogService();
        }
        return instance;
    } 
    
    /**
     * Gets the name of the given object, if a class is specified then the most specialized name
     * of that class will be returned
     */
    public static string getName(object obj)
    {
        return obj != null ? string.valueOf(obj).substringBefore(':') : null;
    }

    /**
     * Creates and saves the system log.
     * If log creation fails, the log information will be outputted to system.debug() instead
     *
     * @param logType Log type.
     * @param message Error message.
     * @param stackTrace Exception stack trace.
     * @param relatedObjectId Identifier of the related SFDC object which resulted in the error.
     */
    public static void addLog(string logType, string message, string stackTrace, Id relatedObjectId)
    {
        addLog(logType, message, stackTrace, relatedObjectId, null);
    }
    
    /**
     * Creates and saves the system log.
     * If log creation fails, the log information will be outputted to system.debug() instead
     *
     * @param logType Log type.
     * @param message Error message.
     * @param stackTrace Exception stack trace.
     * @param relatedObjectId Identifier of the related SFDC object which resulted in the error.
     * @param data Additional logging data.
     */
    public static void addLog(string logType, string message, string stackTrace, Id relatedObjectId, string data)
    {
        addLog(logType, message, stackTrace, relatedObjectId, data, null);
    }
    
    /**
     * Creates and saves the system log.
     * If log creation fails, the log information will be outputted to system.debug() instead
     *
     * @param logType Log type.
     * @param message Error message.
     * @param stackTrace Exception stack trace.
     * @param relatedObjectId Identifier of the related SFDC object which resulted in the error.
     * @param data Additional logging data.
     * @param name The name of the class or process which generated the error.
     */
    public static void addLog(String logType, String message, String stackTrace, Id relatedObjectId, String data, String name) {
        addLogs(new List<System_Log__c>{createSystemLog(logType, message, stackTrace, relatedObjectId, data, name)});
    }
    
    /**
     * Saves a System_Log__c record with the specified values, but as a future async job
     * If log creation fails, the log information will be outputted to system.debug() instead
     *
     * @param logType Log type.
     * @param message Error message.
     * @param stackTrace Exception stack trace.
     * @param relatedObjectId Identifier of the related SFDC object which resulted in the error.
     */
    @future
    public static void addLogAsync(string logType, string message, string stackTrace, Id relatedObjectId) {
        addLog(logType, message, stackTrace, relatedObjectId, null);
    }
    
    /**
     * Saves a System_Log__c record with the specified values, but as a future async job
     * If log creation fails, the log information will be outputted to system.debug() instead
     *
     * @param logType Log type.
     * @param message Error message.
     * @param stackTrace Exception stack trace.
     * @param relatedObjectId Identifier of the related SFDC object which resulted in the error.
     * @param data Additional logging data.
     */
    @future
    public static void addLogAsync(string logType, string message, string stackTrace, Id relatedObjectId, string data) {
        addLogAsync(logType, message, stackTrace, relatedObjectId, data, null);
    }
    
    /**
     * Creates and saves the system log asynchronously.
     * If log creation fails, the log information will be outputted to system.debug() instead
     *
     * @param logType Log type.
     * @param message Error message.
     * @param stackTrace Exception stack trace.
     * @param relatedObjectId Identifier of the related SFDC object which resulted in the error.
     * @param data Additional logging data.
     * @param name The name of the class or process which generated the error.
     */
    public static void addLogAsync(String logType, String message, String stackTrace, Id relatedObjectId, String data, String name) {
        if (System.isBatch() || System.isFuture())
        {
            addLog(logType, message, stackTrace, relatedObjectId, data, name);
        }
        else
        {
            addLogAsynchronously(logType, message, stackTrace, relatedObjectId, data, name);
        }
    }
    
    /**
     * Creates and saves the system log asynchronously.
     * If log creation fails, the log information will be outputted to system.debug() instead
     *
     * @param logType Log type.
     * @param message Error message.
     * @param stackTrace Exception stack trace.
     * @param relatedObjectId Identifier of the related SFDC object which resulted in the error.
     * @param data Additional logging data.
     * @param name The name of the class or process which generated the error.
     */
    @future
    public static void addLogAsynchronously(String logType, String message, String stackTrace, Id relatedObjectId, String data, String name) {
        addLog(logType, message, stackTrace, relatedObjectId, data, name);
    }
    
    /**
     * Saves System Log entries relating to failed dml operation
     *
     * @param logType Log type.
     * @param ex DML exception to log.
     */
    public static void addLogs(string logType, DmlException ex)
    {
        List<System_Log__c> slList = new List<System_Log__c>();
        //to match a salesforce id
        Pattern p = Pattern.compile('(?<=id\\s)[a-zA-Z0-9]{18}');
        
        for(integer i=0; i<ex.getNumDml(); i++)
        {
            string failedId = ex.getDmlId(i);
            //no id indicated, try to extract an Id from the message
            if(failedId == null) {
                Matcher idMatcher = p.matcher(ex.getDmlMessage(i));
                if(idMatcher.find()) {
                    failedId = idMatcher.group(0);
                }
            }
            Id failedIdConverted = toId(failedId);
            String name = null != failedIdConverted ? failedIdConverted.getSObjectType().getDescribe().getName() : null;

            slList.add(createSystemLog(logType, ex.getDmlMessage(i), ex.getStackTraceString(), failedId, null, name));
        }
        addLogs(slList);
    }

    /**
     * Safely creates list of system log.without risk of an exception causing the original process of failing
     *
     * @param list of System_Log__c
     */
    public static void addLogs(List<System_Log__c> systemLogList) {
        getInstance().InsertForEachError(systemLogList, false);
    }
    
    /**
     * Creates the system log.
     *
     * @param logType Log type.
     * @param message Error message.
     * @param stackTrace Exception stack trace.
     * @param relatedObjectId Identifier of the related SFDC object which resulted in the error.
     * @param data Additional logging data.
     * @param name The name of the class or process which generated the error.
     */
    public static System_Log__c createSystemLog(String logType, String message, String stackTrace, Id relatedObjectId, String data, String name) {
        return new System_Log__c(
                Log_Type__c = logType,
                Name__c = name != null && name.length() > 100 ? name.substring(0, 100) : name,
                Message__c = message.unescapeHtml4(),
                Stack_Trace__c = stackTrace,
                Related_Object_Id__c = relatedObjectId,
                Data__c = data != null ? data.abbreviate(dataFieldLength) : data
        );
    }
    
    /**
     * Creates the system log.
     *
     * @param logType Log type.
     * @param exc Exception to log
     * @param relatedObjectId Identifier of the related SFDC object which resulted in the error.
     * @param data Additional logging data.
     * @param name The name of the class or process which generated the error.
     */
    public static System_Log__c createSystemLog(String logType, Exception exc, Id relatedObjectId, String data, String name) {
        return createSystemLog(logType, exc.getMessage(), exc.getStackTraceString(), relatedObjectId, data, name);
    }
    
    /**
     * Retrieves the stack trace of the current code execution
     *
     * @return The current code stack trace (excluding this method)
     */
    public static string getStackTrace() {
        string stackTrace;
        try
        {
            //no current library method to retrieve current stack trace other than forcing an exception
            integer i = 1 / 0;
        }
        catch(Exception ex)
        {
            stackTrace = ex.getStackTraceString().substringAfter('\n');
        }
        return stackTrace;
    }
    
    /**
     * Returns the Id value of the specified string without throwing an exception
     * Taken from utl_string.toId
     * @param value The string representation of the Id
     * @return The Id value represented by the string, or null if not an Id
     */
    private static Id toId(string value)
    {
        Id idvalue;
        try
        {
            idvalue = null == value ? null : Id.valueOf(value);
        }
        //A string exception is an invalid id, but we needn't do anything with the exception
        catch(StringException ex){
            idvalue = null;
        }
        
        return idvalue;
    }
}

Sample Usage
Code
try
{
    LeadConverter.ConvertLeadsFromEvaluations(productServiceAc.Evaluations__r, ac.Id, ct.Id, true, false);
}
catch(Exception ex)
{
    SystemLogService.addLogAsync(
        const_SystemLog.LOG_TYPE_LEAD_CONV_FAILED,
        ex.getMessage(),
        ex.getStackTraceString(),
        productServiceAc.Id',
        'Convert leads from Evaluations failed',
        'Product_Account'
    );
}