Buffering & Flushing

Smart strategies for buffering and flushing logs in Triton for optimal performance and reliability across Apex and LWC.

Understanding Buffering vs Flushing

  • Apex:

    • addLog(builder) buffers.

    • log(builder) flushes immediately (use at key boundaries or errors).

    • Triton sets stack trace & operation automatically when missing, and appends governor limit info via the builder.

  • LWC:

    • triton.log(builder) buffers in memory.

    • triton.logNow(builder) flushes that one immediately.

    • Triton's LWC TransactionManager auto-flushes after idle periods; still flush on navigation/unload when practical.

Real-World Example: Financial Transaction Processing

public class PaymentProcessor {
    public static void processPayment(Id accountId, Decimal amount) {
        Long startTime = System.now().getTime();
        
        // Set up template for all payment processing logs
        Triton.instance.setTemplate(
            Triton.makeBuilder()
                .category(TritonTypes.Category.Integration)
                .type(TritonTypes.Type.Backend)
                .area(TritonTypes.Area.PaymentProcessing)
                .relatedObject(accountId)
        );
        
        // Buffer routine operations using template
        Triton.instance.addLog(
            Triton.instance.fromTemplate()
                .level(TritonTypes.Level.DEBUG)
                .summary('Payment processing started')
                .details('accountId=' + accountId + ', amount=' + amount)
        );
        
        try {
            // First, make the callout to payment gateway
            HttpRequest req = new HttpRequest();
            req.setEndpoint('https://api.paymentgateway.com/process');
            req.setMethod('POST');
            req.setHeader('Content-Type', 'application/json');
            req.setBody(JSON.serialize(new Map<String, Object>{
                'accountId' => accountId,
                'amount' => amount,
                'currency' => 'USD'
            }));
            
            HttpResponse response = new Http().send(req);
            
            if (response.getStatusCode() == 200) {
                // Parse successful response
                Map<String, Object> responseData = (Map<String, Object>)JSON.deserializeUntyped(response.getBody());
                
                // Now perform DML after successful callout
                Payment__c payment = new Payment__c(
                    Account__c = accountId,
                    Amount__c = amount,
                    Status__c = 'Completed'
                );
                insert payment;
                
                // Buffer success milestone using template
                Triton.instance.addLog(
                    Triton.instance.fromTemplate()
                        .level(TritonTypes.Level.INFO)
                        .summary('Payment processed successfully')
                        .details('paymentId=' + payment.Id)
                        .relatedObject(payment.Id)
                        .integrationPayload(req, response)
                        .duration(System.now().getTime() - startTime)
                );
                
            } else {
                // Immediately flush critical error using template
                Triton.instance.log(
                    Triton.instance.fromTemplate()
                        .exception(new PaymentException('Gateway error: ' + response.getStatusCode()))
                        .summary('Payment gateway failed')
                        .integrationPayload(req, response)
                );
            }
            
        } catch (Exception e) {
            // Immediately flush critical errors using template
            Triton.instance.log(
                Triton.instance.fromTemplate()
                    .exception(e)
                    .summary('Payment processing failed')
            );
            throw e;
        } finally {
            // Flush all buffered logs at transaction boundary
            Triton.instance.flush();
        }
    }
}

LWC Buffering Example

// E-commerce checkout component with smart buffering
export default class CheckoutComponent extends LightningElement {
    triton;
    
    connectedCallback() {
        this.triton = new Triton().bindToComponent('c-checkout');
        
        // Set up template for all checkout operations
        this.triton.setTemplate(
            this.triton.makeBuilder()
                .type(TYPE.FRONTEND)
                .area(AREA.COMMUNITY)
        );
    }
    
    async handleCheckout() {
        // Buffer routine debug info
        this.triton.log(
            this.triton.fromTemplate()
                .level(LEVEL.DEBUG)
                .summary('Checkout initiated')
                .details(JSON.stringify({ 
                    cartItems: this.cartItems.length,
                    totalAmount: this.totalAmount 
                }))
        );
        
        try {
            // Process checkout steps
            await this.validateInventory();
            await this.processPayment();
            await this.createOrder();
            
            // Buffer success milestone
            this.triton.log(
                this.triton.fromTemplate()
                    .level(LEVEL.INFO)
                    .summary('Checkout completed successfully')
                    .details(JSON.stringify({ orderId: this.orderId }))
            );
            
        } catch (error) {
            // Immediately flush critical errors
            await this.triton.logNow(
                this.triton.fromTemplate()
                    .exception(error)
                    .summary('Checkout failed')
            );
            
            // Show user-friendly error
            this.showErrorMessage('Checkout failed. Please try again or contact support.');
        }
    }
    
    disconnectedCallback() {
        // Flush any remaining logs when component is destroyed
        this.triton.flush();
    }
}

When to Buffer vs Flush

Buffer When:

  • Routine operations: Normal processing steps, debug information

  • High-volume scenarios: Processing multiple records in loops

  • Performance-critical paths: Where logging overhead must be minimized

  • Non-critical information: Debug logs, informational milestones

Flush Immediately When:

  • Critical errors: Exceptions that might prevent further processing

  • Transaction boundaries: End of methods, batch chunks, queueable completion

  • User-facing failures: Errors that affect user experience

  • Integration failures: Callout errors, external service issues

  • Component teardown: LWC component destruction

Performance Considerations

Apex Performance

  • Buffering reduces DML: Multiple logs are sent together, reducing platform event overhead

  • Flush strategically: Use flush() at natural boundaries to avoid memory buildup

  • Error scenarios: Always flush on errors to ensure critical information is captured

  • Governor limits: Monitor platform event limits in high-volume scenarios

LWC Performance

  • Memory management: Buffered logs consume memory until flushed

  • Auto-flush: Triton automatically flushes after idle periods

  • Navigation events: Flush on page navigation or component destruction

  • Error handling: Use logNow() for critical errors to ensure immediate capture

Best Practices

Buffer Management

  1. Set clear boundaries: Define when to flush based on logical transaction boundaries

  2. Monitor memory usage: Be aware of buffered log volume in long-running processes

  3. Error handling: Always flush on exceptions to preserve critical context

  4. Performance testing: Measure the impact of buffering vs immediate flushing

Flush Strategies

  1. Method boundaries: Flush at the end of public methods

  2. Batch operations: Flush after each batch chunk

  3. Queueable completion: Flush at the end of queueable execution

  4. Component lifecycle: Flush in LWC disconnectedCallback()

  5. Error scenarios: Immediate flush for all exceptions

Monitoring and Debugging

  1. Log volume: Monitor the number of buffered vs flushed logs

  2. Memory usage: Track memory consumption in long-running processes

  3. Error correlation: Ensure errors are properly flushed for debugging

  4. Performance impact: Measure the overhead of different buffering strategies

Advanced Patterns

Conditional Flushing

public void processRecords(List<SObject> records) {
    for (SObject record : records) {
        // Buffer routine processing
        Triton.instance.addLog(
            Triton.instance.fromTemplate()
                .summary('Processing record')
                .relatedObject(record.Id)
        );
        
        try {
            processRecord(record);
        } catch (Exception e) {
            // Flush immediately on error
            Triton.instance.log(
                Triton.instance.fromTemplate()
                    .exception(e)
                    .summary('Record processing failed')
            );
            throw e;
        }
    }
    
    // Flush all buffered logs at the end
    Triton.instance.flush();
}

Smart Buffering with Limits

public class SmartBuffer {
    private static final Integer MAX_BUFFER_SIZE = 50;
    private static Integer bufferCount = 0;
    
    public static void addLog(TritonBuilder builder) {
        Triton.instance.addLog(builder);
        bufferCount++;
        
        // Flush when buffer gets too large
        if (bufferCount >= MAX_BUFFER_SIZE) {
            Triton.instance.flush();
            bufferCount = 0;
        }
    }
    
    public static void forceFlush() {
        Triton.instance.flush();
        bufferCount = 0;
    }
}

By mastering buffering and flushing strategies, you'll optimize performance while ensuring critical logs are never lost and debugging information is always available when needed.

Last updated