# Batch Logging

Batch jobs are great for processing large amounts of data in the background. They can be scheduled, executed by hand on demand, or via some sort of trigger. The common pain point with these is that there's little visibility when it comes to failures. Hence, many developers turn to logging. There are a number of use cases to cover here: basic error handling, trace logging, and profiling.&#x20;

## Basic Error Handling&#x20;

Proper error handling is essential to weed out those pesky errors in batch jobs. Often Salesforce will mask failures or provide a one-liner error message with no stack trace. This is where we can make a huge difference by taking matters into our own hands. Below is a code snippet that incorporates error logging in the most troublesome *execute()* method.

<pre class="language-apex" data-title="Basic Batch Logging" data-line-numbers><code class="lang-apex"><strong>public class LeadsBatch implements Database.Batchable&#x3C;sObject>, Database.Stateful {
</strong>    private String query ='';
    private Triton logger = Triton.instance;
    
    public LeadsBatch(String leadQuery) {
        this.query = leadQuery;
    }
    
    public Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator(query);
    }
    
    public void execute(Database.BatchableContext bc, List&#x3C;Lead> leads) {
        for(Lead l : leads){
            try {
                LeadProcessingResult result = LeadService.getQualificationScore(l);
                if(result.isSuccess) {
                    l.Score__c = result.qualificationScore;
                }
            } catch(Exception e){
                this.logger.addError(TritonTypes.Area.LeadQualification, e);
            }
        }
        
        try {
            update leads;
        } catch(Exception e){
            this.logger.addError(TritonTypes.Area.LeadQualification, e);
        }
    }

    public void finish(Database.BatchableContext bc){
        this.logger.flush();
    }
}
</code></pre>

There is a good chance you might find similar batch jobs in your own org. The pattern is quite typical: query records, process them in a loop, then perform a DML, a callout or some other action. Let's now dive into the details of what's happening here.&#x20;

* Note the use of the ***Database.Stateful*** interface in the declaration of our *LeadsBatch* clas&#x73;*.* In this example we have made our batch **stateful**, or, in other words, retain its state between the different iterations of the *execute()* method. For more information on this please refer to [Salesforce developer documentation here.](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_batch_interface.htm) The reason we need state preservation is to be able to accumulate our logs in a buffer within the logger class variable declared on line (3). Static context in our Singleton logger instance will not be preserved between different batch iterations.
* In this batch class we're utilizing another concept called **bulk logging** [introduced in this section](/pharos-triton/apex-logging-basics.md#bulk-logging). You'll notice the use of the *addError()* method instead of the *error()* method as in the [previous example](#basic-apex-logging). The *addError()* method appends to the log buffer  that is subsequently flushed in the *finish()* method on line (33). &#x20;

{% hint style="info" %}
The main motivation behind buffering logs is to **preserve platform event (PE) allocations** by emitting a single platform event containing a collection of logs. Since we're dealing with a batch, it may take hundreds of iterations to complete. Assuming we log something on every iteration, quite a few PE allocations may be consumed.
{% endhint %}

* The use of the *finish()* method on line (33). This method runs when a batch is finished executing, regardless of whether those executions resulted in exceptions. This is good news for us as it ensures that our log buffer is persisted properly.&#x20;
* Note the use of the *Area* enum on line (21) and (28) in the *catch()* clause. The more accurate you can be with this value, the better your searching and reporting experience will be.

{% hint style="info" %}
It is possible to log within **all** **methods** of the batch class, not just the *execute().* In fact, it is highly recommended you do so when you have logic in those methods. In our example above there is really not much going there, hence no logging needed.
{% endhint %}

## Grouping Logs From Multiple Iterations

Let's now incorporate the idea of uniting logs under a single transaction [illustrated in the very first example](/pharos-triton/common-apex-usage-patters.md#basic-apex-logging) with the concept of bulk logging. We'd like to see all logs for each of the iteration grouped under the same parent, and we are also going to do in a way that is consistent with the platform best practices by emitting only a single platform event. The example below also adds a few *debug()* and *event()*  statements to the mix for a more detailed trace of batch execution:

{% code title="Batch + Transaction" lineNumbers="true" %}

```apex
public class LeadsBatch implements Database.Batchable<sObject>, Database.Stateful {
    private String query ='';
    private Log logger = Triton.instance;
    //will need to keep track of the number of iterations
    private Integer iteration = 0;
    
    public LeadsBatch(String leadQuery) {
        this.query = leadQuery;
        logger.startTransaction();
        logger.addDebug(TritonTypes.Type.LeadsBatch, 
                        TritonTypes.Area.LeadQualification,
                        'LeadsBatch initialized',
                        'LeadsBatch initialized with query: ' + query );
    }
    
    public Database.QueryLocator start(Database.BatchableContext bc) {
        logger.addEvent(TritonTypes.Level.FINE,
                    TritonTypes.Type.LeadsBatch, 
                    TritonTypes.Area.LeadQualification,
                    'LeadsBatch starting',
                    'Fetching query locator for query: ' + query );
        return Database.getQueryLocator(query);
    }
    
    public void execute(Database.BatchableContext bc, List<Lead> leads) {
        iteration++;
        logger.addEvent(TritonTypes.Level.FINE,
                    TritonTypes.Type.LeadsBatch, 
                    TritonTypes.Area.LeadQualification,
                    'LeadsBatch executing iteration: '+ iteration,
                    'Processing ' + leads.size()) + ' in this iteration';
        for(Lead l : leads) {
            try {
                LeadProcessingResult result = LeadService.getQualificationScore(l);
                if(result.isSuccess) {
                    l.Score__c = result.qualificationScore;
                } else {
                    logger.addEvent(TritonTypes.Level.FINE,
                                    TritonTypes.Type.LeadsBatch, 
                                    TritonTypes.Area.LeadQualification,
                                    'LeadsBatch failed to obtain successful qualification score',
                                    'Result returned for lead id ' + l.Id + 
                                        Triton.SPACE_SEP + 'Result: ' + 
                                        Triton.SPACE_SEP + String.valueOf(result);
                }
            } catch(Exception e){
                logger.addError(TritonTypes.Area.LeadQualification, e);
            }
        }
        
        try {
            update leads;
        } catch(Exception e){
            logger.addError(TritonTypes.Area.LeadQualification, e);
        }
    }

    public void finish(Database.BatchableContext bc){
        logger.addDebug(TritonTypes.Type.LeadsBatch, 
                        TritonTypes.Area.LeadQualification,
                        'LeadsBatch finishing',
                        'Iterations processed: ' + iteration);
        logger.flush();
        logger.stopTransaction();
    }
}
```

{% endcode %}

For the most part this example is similar in spirit to the basic batch logging snippet. A few things to highlight here nonetheless:

* Note the more generous logging statements that are incorporated into every method of this batch class. This will provide more details when analyzing the execution and help examine some key data points such as iteration number and the query that was used to construct a query locator.&#x20;
* In this example we're making use of the different log levels (see [Level enum](/pharos-triton/apex-logging-basics.md#h_01hsyh0nmy90sa21hdbktchavj-1)). The constructor and the *finish()* methods are utilizing the DEBUG level, whereas the *start()* and *execute()* methods are logging at INFO level.&#x20;

{% hint style="info" %}
The general rule of thumb with log levels is to log anything that is essential for troubleshooting at DEBUG, WARNING or INFO levels. Everything else that may be helpful should remain at the FINE level or below.&#x20;

Errors are typically always logged at the highest level of ERROR.
{% endhint %}

* Last but not least is that all these logs that we're generating here will be grouped under a single parent log. This is quite convenient for future troubleshooting needs as there is now an nice easy to navigate structure available.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://triton.pharos.ai/pharos-triton/common-apex-usage-patters/batch-logging.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
