25

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?

  • 1
    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. – techtrekker Apr 12 '13 at 14:38
  • Aren't the results of future methods also rolled back?! – Robert Sösemann Apr 13 '13 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/… – techtrekker Apr 13 '13 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. – ca_peterson Apr 14 '13 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? – Robert Sösemann Apr 18 '13 at 11:17
25

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

  • 5
    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. – ca_peterson Apr 14 '13 at 18:28
  • No problem Robert, happy to help! – Andrew Fawcett Apr 14 '13 at 19:36
  • @Andrew: Would that also work if in my test code a LimitException occurs? I thought those are generally not catchable. – Robert Sösemann Apr 18 '13 at 11:15
  • No sadly not, hence my final note in the answer above. – Andrew Fawcett Apr 18 '13 at 15:44
  • How come the rollback doesn't roll back the logs? – greenstork Nov 4 '13 at 17:55
3

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
   }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.