28

I do some custom logging in my code, which basically means I write and update SObject records that contain custom log information.

The problem is whenever an exception is thrown in my class, the already inserted SObjects are rolled back.

Can I force them to stay persisted?

6
  • 2
    The transaction is rolled back when an unhandled exception occurs, so for starters handle the exception and rollback to the checkpoint just before the transaction started need be ? Another thought is delegate logging to @future methods. Apr 12, 2013 at 14:38
  • 1
    Aren't the results of future methods also rolled back?! Apr 13, 2013 at 21:11
  • Apparently there are, I didn't realise this. Although I wonder if I've made a ws-callout in a future, there's no way of rolling it back surely ? salesforce.stackexchange.com/questions/1496/… Apr 13, 2013 at 21:43
  • Emails are also rolled back. There's no way of persisting anything through a rollback other than making a webservice callout before it happens. Apr 14, 2013 at 18:25
  • What about RemoteActions instead of Webservice callouts from org X to org X. Wouldn't that be a leaner solution and also be not rolled back? Apr 18, 2013 at 11:17

3 Answers 3

27

This is possible, but first a word on transaction scope and management...

Transaction Scope: It is important to understand a bit more about how the platform handles the transaction for DML operations executed. By default if you don't catch exceptions the platform will rollback any changes the request (controller action method etc) automatically. This helps avoid the situation where some records are written (those leading up to the error) and others not. If your planning on catching exceptions in your UI (as is most common in VF) or logging reasons you need to consider this.

Logging and Savepoints: The following illustrates both a simple logging pattern and how you might want to consider wrapping you logic up in Savepoint to manage the rollback consistently. The approach used here is to store up your log entries until the 'finally' block, then insert them into the database.

public with sharing class Logger 
{       
    public static void demo()
    {
        // Rollback to here if any errors occuring in the following
        System.Savepoint sp = Database.setSavepoint();
        try
        {
            // Perform some work
            Logger.log('Creating Test');
            Test__c test = new Test__c();
            insert test;
            Logger.log('Created Test');

            // Do some calculations that might generate exceptions...
            Integer bad = 0;
            bad = bad / 0;
            Logger.log('Done some calcs');

            // Perform some other work (which might also cause an exception)
            insert new Test_Child__c(Test__c = test.Id); 
            Logger.log('Created Test Child');
        }
        catch (Exception e)
        {
            // Log the exception
            Logger.log(e);  

            // Rollback everything done up until this point
            Database.rollback(sp);          
        }
        finally
        {   
            // Flush the log writing everything captured to the database        
            Logger.flush();
        }
    }

    private static List<Log__c> logs = new List<Log__c>();

    public static void log(String message)
    {
        logs.add(new Log__c(Message__c = message));     
    }

    public static void log(Exception e)
    {       
        logs.add(new Log__c(Message__c = e.getMessage(), StackTrace__c = e.getStackTraceString()));
    }

    public static void flush()
    {
        insert logs;    
    }
}

The following shows a successful log output and a failed one (with the divide by zero code in above).

enter image description here

Finally if your interested in a pattern that helps you better manage transactional scope (without having to liter your code with Savepoint logic), avoid the DML governor and a few other features, take a look at the Unit Of Work pattern described here.

NOTE: There are a few exceptions that cannot be caught in Apex (governors being the main example), in this case you cannot use your own logging approach as like it or not the platform will rollback everything here as the finally will never be called. You may want to consider using Subscriber Support access to view the platform debug logs in this cases. Maybe then even enhance the Logger.log method to also emit to System.debug (utilise LoggingLevel.Error will ensure messages are not truncated in larger logs).

7
  • 6
    If you're going to use system.debug it's worth passing LoggingLevel.Error as the first argument to help prevent your debug lines from being truncated in larger logs. Apr 14, 2013 at 18:28
  • No problem Robert, happy to help! Apr 14, 2013 at 19:36
  • @Andrew: Would that also work if in my test code a LimitException occurs? I thought those are generally not catchable. Apr 18, 2013 at 11:15
  • No sadly not, hence my final note in the answer above. Apr 18, 2013 at 15:44
  • 1
    Why does the insert logs not get rolled back I think is the question. But the answer is because the insert is happening in the finally statement, no logs were saved to the database before the exception, only after.
    – Shanerk
    Feb 15, 2021 at 15:46
5

As per How can I cause side effects outside an execution context?, Summer '17 now has Platform Events GA.

Note the following from the docs:

Platform Events and Transactions
Unlike custom objects, platform events aren’t processed within database transactions in the Force.com platform. As a result, publishing platform events can’t be rolled back. Note the following:

  • The allOrNoneHeader API header is ignored when publishing platform events through the API.
  • The Apex setSavepoint() and rollback() Database methods aren’t supported with platform events.

So any Platform Events that you publish before the transaction rollback occurs will be persisted. You could use a trigger on those events to insert the actual sObjects.

2

As long as the exception isn't fatal, you should be able to prevent the rollback. Are you able to add try-catch blocks to handle the types of exceptions you're experiencing or expect, perhaps log them and then exit gracefully (or continue if that's appropriate)?

Edited to add... I probably should have provided an example as below:

try{
If(lstsObject.fieldA==null) lstsObject.fieldA = 0;
   }catch(nullpointerexception){
     lstsObject.fieldA = 0;
   }catch(listexception){
      // list exception error
      // do something else A;
   }catch(exception e){
     // generic exception error
     //do something else B;
   }finally{
     // optional finally block 
     // code to run whether there's an exception or not
   }

You must log in to answer this question.

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