5

I have a similar question to these two: Custom Logging and Future Methods Custom Exception Handling & Logging

But my question this time is: can I put the @Future annotation on the Logger Class? Why would this not allow me to process DML even if an exception is thrown?

Scenario: I have a custom Logger class that inserts a custom record with a error message. The logger class is called at the end of every catch statement. The only way for the Logger class to insert a new record is if we do not re-throw the caught exception. I do not want to do this because if there is an exception, this indicates something is broken which should halt the entire process. This leading me to never be able to insert a new Log error record.

My thought is: if the logger method runs in the future, is it possible for the process to run, all exceptions are thrown, the process is halted, AND THEN my log message DML happens outside of the transaction?

For example:

global with sharing class NFLogger {
private enum LogLevel {Error, Info}

@Future
public static void logError(String tags, String errorMessage){
    logMessage(logLevel.Error, tags, errorMessage, null);
}

@Future
public static void logError(String tags, String errorMessage, String ex){
    logMessage(logLevel.Error, tags, errorMessage, ex);
}

@Future
public static void logInfo(String tags, String infoMessage){
    logMessage(logLevel.Info, tags, infoMessage, null);
}

private static void logMessage(LogLevel level, String tags, String message, String ex){
    try {
        system.debug('Level = ' + level + ', tags = ' + tags + ', message = ' + message + ', system exception = ' + ex);
        LogMessage__c newMessage = new LogMessage__c(level__c = level.name(), tags__c = tags, message__c = message, exception__c = ex);
        insert newMessage;
    }
    catch (Exception e){
        //intentionally "we suppress" any exception
        //we need to guarantee that logMessage will never throw any exceptions
        system.debug('in the catch of NFLogger');
    }
}
}

APEX:

    ...
    try{
        TokenGenerator tg = new TokenGenerator();
        String tokenizedPayload = tg.getTokenizedPayloadForSingleLead(pLead, nfId); //WEBSERVICE
        return new pageReference('/apex/DLPEmailLinkUpdateSvc?id='+pLead+'&tokencred='+tokenizedPayload+'&data='+data+'&ptype=lead');
    } catch (Exception ex) {
        Apexpages.addMessages(ex);
        NFLogger.logError('SendDLPRequestLead, tokenizer', 'There has been an error in the SendDLPRequestLead on Lead: ' + pLead, ex);
        throw ex;
    }

2 Answers 2

5

Nope, you can't do that with a future method, or with a Queueable, or Batch Apex, because all of those asynchronous invocations are enqueued for execution after the transaction's over - and that enqueueing is rolled back when an unhandled exception is thrown just like DML is.

There is, however, one way you can get those logs out that's rollback-proof: Platform Events.

Unlike custom objects, platform events aren’t processed within database transactions in the Lightning platform. As a result, published platform events can’t be rolled back.

If what you really want is to throw an exception and roll back the transaction, but also persist some logging information, publish a Platform Event in your handler. Then, the trigger associated with your Platform Event can do the work needed to persist the log details into the database. That persisting takes place in a separate transaction, as the Automated Process user, and is not subject to the rollback of your original transaction.

That said, I'm not convinced that letting an unhandled exception propagate is the correct choice in the situations you're showing in your code. In backend code, yes. But - acknowledging that there are many schools of thought on exceptions across the software development world - I don't like unhandled exceptions in my UI code (Visualforce and Lightning controllers).

The user experience is not ideal in those cases, so I prefer to catch and handle them by presenting a user-friendly message. In Lightning, however, that does entail re-raising an AuraHandledException.

4
  • I was just writing about Platform Events :P Commented Nov 28, 2018 at 16:40
  • Just to add in some edge cases, Future methods do get called out even if there was an exception in Trigger context. smgoodyear.com/2015/12/04/… Commented Nov 28, 2018 at 16:46
  • 1
    @PranayJaiswal That's quite interesting, I need to spend some time going through that use case. I wonder if there was some hidden retry mechanism in the data loader that produced the result.
    – David Reed
    Commented Nov 28, 2018 at 17:09
  • 1
    the retry mechanism in data loader is allOrNone=false and that SFDC will auto-retry the successes in the batch per this bit of doc. This will cause re-executions if you don't guard against this.
    – cropredy
    Commented Nov 28, 2018 at 17:56
2

For your question:

is it possible for the process to run, all exceptions are thrown, the process is halted, AND THEN my log message DML happens outside of the transaction?

One of the approaches you can address this scenario is using try-catch-finally as finally block is always guaranteed to be executed as long as it's not an uncatchable exception. So your exceptions are handled, and only then you log the message by keeping your flow synchronous in nature without getting into complexities of handling this asynchronously.

Code in the finally block always executes regardless of whether an exception was thrown or the type of exception that was thrown. Because the finally block always executes, use it for cleanup code, such as for freeing up resources.

Sample below.

Based on a quick test, this approach will work if you are handling the exception in the catch block and not rethrowing it.

Boolean isException = false;
Exception myEx;
try {
....
} catch (Exception e) {
    isException = true;
    myEx = e;
    // handle exception
} finally {
    // verify if exception occurred, then take the call
    if(isException) {
        // log messages
    }
}
8
  • 1
    +1 , I believe finally was made for such things, that should happen even if there is abnormal in execution. Commented Nov 28, 2018 at 16:48
  • 1
    Do future methods invoked in finally blocks get executed if an exception is re-thrown? I can't make that happen with a simple test class.
    – David Reed
    Commented Nov 28, 2018 at 16:54
  • @DavidReed - I haven't really tried it. But if doesn't then that will be interesting find. My PoV here is that if at all there's a piece of code that we always want to be executed after exception handling, then use finally and let that be synchronous. I am recommending to take away asynchronous approach completely here.
    – Jayant Das
    Commented Nov 28, 2018 at 16:57
  • 1
    The overall point may be that re-throwing is a pattern that has relatively limited utility on the Salesforce platform.
    – David Reed
    Commented Nov 28, 2018 at 17:01
  • 1
    I quickly verified this, if you handle the exception, this approach works fine, but if you rethrow an exception, it doesn't.
    – Jayant Das
    Commented Nov 28, 2018 at 17:18

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .