4

This may be simple, but I want to implement custom error logging for a batch class, where handled exceptions get written to a logging object in the database.

So I could use the technique in Andrew's answer here: Can I prevent an insert to be rolled back even if my class (later) runs into an exception?

The wrinkle is that if a handled exception occurs and is logged, I'd still like that batch to count as a "Failure" so that when the user monitors the job in Apex Jobs, it'll still show in the failed batches count.

enter image description here

Make sense? I'm handling the exceptions in this case in order to log them, not because I want them hidden from the user.

Is it possible to do this, and still have the log survive? If I use Andrew's technique, and catch the original err, do a rollback, write the logs, and then throw a new exception, it'll roll back the logs, right? And if I don't do the throw, the batch will count as a success, right?

2 Answers 2

7

Yes, you can capture your logs and still have individual batches fail and rollback. For that you need to define an instance variable in your batch class - List of custom object used for logging - to store your logs while batch is running and insert them all at once in the finish() method. You will have to add Database.Stateful to your class definition (see 'Using State in Batch Apex' in this document) to ensure that your instance variable is preserved across transactions.

global class TestBatch implements Database.Batchable<sObject>, Database.Stateful {

    global List<Log__c> logs;

    global TestBatch() {
        logs = new List<Log__c>();
    }

    global Database.QueryLocator start(Database.BatchableContext BC) {
        query = 'Your query...';
        return Database.getQueryLocator(query);
    }

    global void execute(Database.BatchableContext BC, List<sObject> scope) {
        for (sobject s : scope) {
            try{
                Your logic goes here...
            } catch (Exception e) {
                logs.add(new Log__c(
                        Name__c = '...',
                        Stacktrace__c = e.getStackTraceString()
                    ));
                throw e;
            }
        }
        update scope;
    }

    global void finish(Database.BatchableContext BC) {
        insert logs;
    }
}
4
  • Aha. So in order to do this, I would need to Catch the original err, create the log record in memory, and then Throw a new exception to make the batch fail? I'm already using Stateful so no problem there.
    – mscholtz
    May 2, 2014 at 0:31
  • Yes, that's right.
    – IvanR
    May 2, 2014 at 7:02
  • I'm going to try this, and let you know if it works. Thanks much!
    – mscholtz
    May 2, 2014 at 17:35
  • Did a test, this seems to work great. I'm taking a hybrid approach where I write the logs at the end of the execute method if it gets that far. Then I do it again in the finish method to catch any remaining at the end. That way, if I have a lot of errors, but not on every batch, it keeps the heap size and eventual DML size on the log insert down.
    – mscholtz
    May 13, 2014 at 23:57
0

This is my understanding of how the platform works by default: if any unhandled exception (exception that is not caught by our code) occurs, or if any record in the context is flagged as containing an error through the use of the addError method, the whole transaction is rolled back. This makes it impossible to store custom errors or to send error notifications, as the storing of the error records or the Chatter post records would also be rolled back. The emails containing error notifications would never be sent, as the transaction rollback pulls them from the queue they were put in.

The only alternative is the workaround mentioned above, in which exceptions are caught and addError is not used. However in this case the platform has no way of knowing that an error occurred.

You must log in to answer this question.

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