> For the complete documentation index, see [llms.txt](https://triton.pharos.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://triton.pharos.ai/triton-best-practices/buffering-and-flushing.md).

# Buffering & Flushing

The discipline that keeps you under platform-event limits without losing the logs that matter — buffer the routine, publish the critical, flush at boundaries.

Triton publishes logs as platform events, and Salesforce caps how many of those you can fire per transaction. Buffering is how you stay under that ceiling: queue logs in memory, then publish them together. The full mechanics — `flush`, `flushTop`, `drain`, and the automatic backoff when allocations run low — are in [Core Concepts → Buffering, Flushing & Deferred Publishing](/core-concepts/buffering-flushing.md). This page is the practical rule you actually apply.

## The one rule

**Buffer the routine. Publish the critical immediately. Flush at boundaries.**

That's it. Everything below is just that rule applied to specific situations.

Here's the shape it takes in almost every method you'll write:

{% code lineNumbers="true" %}

```apex
try {
    for (Account a : scope) {
        // ... work ...
        Triton.info(Triton.t.summary('Processed ' + a.Id));   // buffered — cheap
    }
    Triton.flush();   // publish the whole batch as one platform event
} catch (Exception e) {
    Triton.logNow(Triton.t.exception(e));   // never lose this one
}
```

{% endcode %}

The `Triton.info(...)` calls buffer. The single `Triton.flush()` publishes them all together. The exception goes out immediately via `logNow` so a mid-loop failure can't take your logs down with it.

{% hint style="info" %}
**Remember the 2.0 default flipped.** All the level methods — `Triton.info`, `Triton.error`, `Triton.warning`, and friends — **buffer**. You opt *into* immediate publishing with `Triton.logNow(...)`. If you're coming from 1.x, where bare `error(...)` published instantly, audit that assumption. See [Migrating from Triton 1.x to 2.0](/migration.md).
{% endhint %}

## When to buffer

Buffer anything that's part of the normal, happy path and can safely wait until a flush:

* **Progress and milestones** — "step 1 done", "240 records processed".
* **Loop-body diagnostics** — per-record `FINE` logs where you'd otherwise fire hundreds of events.
* **Debug context** — the breadcrumbs you leave for your future self.

In a trigger over 200 records, buffering is the difference between **one** platform event and **two hundred**. This isn't a micro-optimization; it's the difference between a logging strategy that scales and one that gets switched off after the first governor-limit incident.

## When to publish immediately

Reach for `Triton.logNow(...)` when losing the log would hurt:

* **Exceptions you must not lose** — especially before a `throw` that ends the transaction.
* **Failures right before a risky operation** — a callout, a `Database.` operation, anything that might blow up and skip your flush.
* **The last thing you'll ever log in this path** — if there's no reliable flush after it, publish it now.

{% code lineNumbers="true" %}

```apex
try {
    chargeCard(payment);
} catch (Exception e) {
    Triton.logNow(Triton.t.area(TritonTypes.Area.OpportunityManagement).exception(e));
    throw e;   // safe — the log is already persisted
}
```

{% endcode %}

## Where to flush

Flush at the natural seams of your code, where you know the work is done and the transaction is still healthy:

* **End of a trigger handler.**
* **End of a batch `execute`** (each chunk is its own transaction — flush per chunk).
* **End of an `@AuraEnabled` method or REST handler.**
* **In a `finally` block**, when there's real risk of an exception between your logs and the flush.

{% hint style="warning" %}
**A buffered log that never flushes is a log you'll wish you had.** The classic bug: you buffer progress logs, then an exception fires before your `flush()` line, and the whole buffer evaporates. Two defenses — put the `flush()` in a `finally`, and publish anything genuinely critical with `logNow` the moment you create it.
{% endhint %}

## The `finally` pattern, in full

This is the shape worth memorizing for anything non-trivial:

{% code lineNumbers="true" %}

```apex
public static void processPayment(Id accountId, Decimal amount) {
    Long startTime = System.now().getTime();
    Triton.setTemplate(Triton.makeBuilder()
        .category(TritonTypes.Category.Apex)
        .area(TritonTypes.Area.OpportunityManagement)
        .relatedObject(accountId));

    Triton.debug(Triton.t.summary('Payment started').details('amount=' + amount));   // buffered

    try {
        HttpResponse res = callGateway(accountId, amount);
        if (res.getStatusCode() == 200) {
            Payment__c p = new Payment__c(Account__c = accountId, Amount__c = amount, Status__c = 'Completed');
            insert p;
            Triton.info(Triton.t                                   // buffered
                .summary('Payment processed')
                .relatedObject(p.Id)
                .duration(System.now().getTime() - startTime));
        } else {
            Triton.logNow(Triton.t                                 // published now
                .warning()
                .summary('Payment gateway returned ' + res.getStatusCode()));
        }
    } catch (Exception e) {
        Triton.logNow(Triton.t.exception(e));                      // published now
        throw e;
    } finally {
        Triton.flush();   // guarantees the buffered logs go out, success or failure
    }
}
```

{% endcode %}

## What Triton handles for you

Two things you don't have to manage:

* **Near-limit backoff.** When platform-event allocation approaches \~95%, `flush()` and `flushTop()` automatically switch to scheduled, backed-off publishing instead of pushing you over the limit. Your logs still go out — just spread over time. Nothing to configure.
* **`drain()` for very high-volume producers.** Flows that would burn allocations inline hand their buffer to a batch that publishes later. You rarely call `drain()` in Apex yourself; it powers the Flow flush batch. See [Core Concepts → Buffering, Flushing & Deferred Publishing](/core-concepts/buffering-flushing.md).

## Async: each execution is its own transaction

In Batch and Queueable, statics reset between executions — so the buffer doesn't carry across chunks. Flush **within** each `execute` (typically at the end), not once at the very end of the job. The [Batch Logging](/common-apex-usage-patters/batch-logging.md) pattern shows this in context.

## Related reading

* The methods and the near-limit safety net in depth: [Core Concepts → Buffering, Flushing & Deferred Publishing](/core-concepts/buffering-flushing.md).
* Applying the pattern per context: [Implementation Patterns](/triton-best-practices/implementation-patterns.md).
