Exception/Error Handling in Salesforce Batch Apex

Batch Apex classes can fire platform events when encountering an error or exception. Event records provide more granular tracking of errors than the Apex Jobs UI because they include the record IDs being processed, exception type, exception message, and stack trace. You can also incorporate custom handling and retry logic for failures. Clients listening on an event can tell how often it failed, which records were in scope at the time of failure, and other exception details. Events are also fired for Salesforce Platform internal errors and other “uncatchable” Apex exceptions like LimitExceptions that are caused by reaching governor limits.

If the start, execute, or finish method of a batch Apex job encounters an unhandled exception, a BatchApexErrorEvent platform event is fired. To fire a platform event, a batch Apex class declaration must implement the Database.RaisesPlatformEvents interface. 

Salesforce Reference Article:
https://help.salesforce.com/s/articleView?id=000340132&type=1
 
Error Log custom object:
 

Sample Code:
Batch class:
global class BatchAccountUpdate implements Database.Batchable<sObject>, Database.RaisesPlatformEvents {
 
    global Database.QueryLocator start( Database.BatchableContext BC ) {
 
        String query = 'SELECT Id,Name FROM Account';
        return Database.getQueryLocator( query );
 
    }
   
    global void execute( Database.BatchableContext BC, List< Account > scope ) {
 
         for ( Account a : scope ) {
         
             a.Name = null;    
             
         }
         update scope;
 
    }   
    
    global void finish( Database.BatchableContext BC ) {
    }
 
}

Trigger on BatchApexErrorEvent:
trigger BatchApexErrorEventTrigger on BatchApexErrorEvent (after insert) {
    
    List < Error_Log__c > errorLogs = new List < Error_Log__c >();
    
    for(BatchApexErrorEvent evt:Trigger.new) {
        
        Error_Log__c objErrorLog = new Error_Log__c(
            Record_Ids__c = evt.JobScope,
            Error_Message__c = evt.Message,
            Job_Id__c = evt.AsyncApexJobId
        );
        errorLogs.add( objErrorLog );
        
    }
    
    if ( errorLogs.size() > 0 ) {
        
        insert errorLogs;
        
    }

}

Sample Error Log: