Implementation Patterns

Real-world implementation patterns for Triton logging across different Salesforce contexts including triggers, batches, queueables, and flows.

Where to Insert Logs

General pattern:

  • Entry: Start/resume transaction, log intent (INFO).

  • Milestones: Key steps with metrics (INFO/DEBUG + duration, counts).

  • Decision points: Branches affecting outcome (DEBUG).

  • External calls: Before/after callouts (INFO, duration; attach sanitized request/response).

  • Errors/Recovery: Catch, exception(...), add full context, immediately flush.

  • Exit: Summarize result and timing, flush.

Triggers (Handler)

public with sharing class AccountTriggerHandler {
    public static void beforeUpdate(List<Account> newList, Map<Id, Account> oldMap) {
        Triton.instance.startTransaction();
        Triton.instance.setTemplate(
            Triton.makeBuilder()
                .category(TritonTypes.Category.Apex)
                .type(TritonTypes.Type.AccountTrigger)
                .area(TritonTypes.Area.Accounts)
                .createdTimestamp()
        );

        Set<Id> scopeIds = new Map<Id, Account>(newList).keySet();

        Triton.instance.log(
            Triton.instance.fromTemplate()
                .level(TritonTypes.Level.INFO)
                .summary('Account before update')
                .details('count=' + scopeIds.size() + ', op=' + Trigger.operationType)
                .relatedObjects(scopeIds)
        );
    }
}

Queueable

public class PriceSyncQueueable implements Queueable, Database.AllowsCallouts {
    private final Set<Id> productIds;
    public PriceSyncQueueable(Set<Id> productIds) { this.productIds = productIds; }

    public void execute(QueueableContext qc) {
        Triton.instance.startTransaction();
        Triton.instance.setTemplate(
            Triton.makeBuilder()
                .category(TritonTypes.Category.Integration)
                .type(TritonTypes.Type.Backend)
                .area(TritonTypes.Area.ProductCatalog)
                .relatedObjects(productIds)
        );

        Long started = System.now().getTime();
        try {
            // ... callout(s) and DML
            Triton.instance.log(
                Triton.instance.fromTemplate()
                    .level(TritonTypes.Level.INFO)
                    .summary('Price sync complete')
                    .details('synced=' + productIds.size())
                    .duration(System.now().getTime() - started)
            );
        } catch (Exception e) {
            Triton.instance.log(
                Triton.instance.fromTemplate()
                    .exception(e)
            );
            throw e;
        } finally {
            Triton.instance.stopTransaction();
        }
    }
}

Batchable

global class LeadQualifyBatch implements Database.Batchable<SObject>, Database.Stateful {
    global Database.QueryLocator start(Database.BatchableContext bc) {
        Triton.instance.startTransaction();
        Triton.instance.setTemplate(
            Triton.makeBuilder()
                .category(TritonTypes.Category.Apex)
                .type(TritonTypes.Type.Backend)
                .area(TritonTypes.Area.LeadConversion)
        );
        Triton.instance.log(Triton.instance.fromTemplate()
            .level(TritonTypes.Level.INFO)
            .summary('Lead qualify start')
            .details('scope=all open leads'));
        return Database.getQueryLocator([SELECT Id, Status FROM Lead WHERE IsConverted = FALSE]);
    }

    global void execute(Database.BatchableContext bc, List<Lead> scope) {
        Long t0 = System.now().getTime();
        try {
            // ... work
            Triton.instance.log(Triton.instance.fromTemplate()
                .level(TritonTypes.Level.INFO)
                .summary('Executed batch chunk')
                .details('size=' + scope.size())
                .duration(System.now().getTime() - t0)
                .relatedObjects(new Map<Id, Lead>(scope).keySet()));
        } catch (Exception e) {
            Triton.instance.log(
                Triton.instance.fromTemplate()
                    .exception(e)
                    .summary('Batch chunk failed')
                    .relatedObjects(new Map<Id, Lead>(scope).keySet())
            );
            // Continue or rethrow per design
        }
    }

    global void finish(Database.BatchableContext bc) {
        Triton.instance.log(Triton.instance.fromTemplate()
            .level(TritonTypes.Level.INFO)
            .summary('Lead qualify finished'));
        Triton.instance.stopTransaction();
    }
}

LWC – With @wire

import Triton, { LEVEL, TYPE, AREA } from 'c/triton';
import getCases from '@salesforce/apex/CaseController.getCases';
import { wire } from 'lwc';

const triton = new Triton().bindToComponent('c-case-list');
triton.setTemplate(triton.makeBuilder().type(TYPE.FRONTEND).area(AREA.COMMUNITY));

@wire(getCases, { contactId: '$recordId', txId: '$triton.transactionId' })
wiredCases({ data, error }) {
  if (data) {
    triton.log(
      triton.fromTemplate()
        .summary('Wire loaded')
        .details(`rows=${data.length}`)
        .relatedObjects([this.recordId])
    );
  } else if (error) {
    triton.logNow(
      triton.fromTemplate()
        .exception(error)
        .summary('Wire error')
        .relatedObjects([this.recordId])
    );
  }
}

LWC – Imperative

const triton = new Triton().bindToComponent('c-order-console');
const txId = triton.startTransaction();

// Set up template for order operations
triton.setTemplate(
    triton.makeBuilder()
        .type(TYPE.FRONTEND)
        .area(AREA.REST_API)
        .relatedObjects([this.recordId])
);

try {
  const data = await loadOrders({ accountId: this.recordId, txId });

  triton.log(
    triton.fromTemplate()
      .summary('Orders loaded')
      .details(`count=${data.length}`)
  );
} catch (e) {
  triton.logNow(
    triton.fromTemplate()
      .exception(e)
      .summary('Load orders failed')
  );
} finally {
  triton.stopTransaction();
}

Flow

Use the Triton invocable action (Add Log) to log milestones and errors from flows. This declarative approach integrates seamlessly with Flow Builder and provides comprehensive logging capabilities.

Key Flow Logging Strategies:

  • Strategic Placement: Add log actions at critical points:

    • Flow Start: Capture initiation and input parameters

    • User Interactions: Record form submissions and decisions

    • Data Operations: Log before/after DML operations and callouts

    • Decision Points: Document which logic branches are followed

    • Error Paths: Add detailed logging in fault connectors

  • Essential Parameters:

    • Interview GUID: Always use $Flow.InterviewGuid for proper log grouping and error correlation

    • Area: Set to appropriate business domain (e.g., Accounts, Opportunities)

    • Category: Typically Flow for most logging scenarios

    • Type: Specify flow type (e.g., Screen Flow, Autolaunched Flow)

    • Summary: Brief description of the log entry

    • Details: Include record IDs, decision variables, and contextual data

  • Best Practices:

    • Use Constants: Create constants for common parameters (Area, Category, Type) to maintain consistency

    • Text Templates: Use text templates for log details to combine static text with dynamic flow data

    • Naming Conventions: Prefix resources with "Log_" for easy filtering

    • Additional Fields: Use JSON-formatted additional fields for extra contextual data when needed

  • Automatic Error Capture: Triton automatically captures unhandled Flow exceptions and correlates them with your custom logs using the Interview GUID, providing enhanced debugging information including stack traces, user context, and related object details.

  • Log Grouping: All logs from the same Flow instance are automatically grouped under a parent log using the Interview GUID, creating a complete execution trace for troubleshooting.

Integration Patterns

REST API Controllers

@RestResource(urlMapping='/api/accounts/*')
global with sharing class AccountRestAPI {
    
    @HttpGet
    global static void getAccount() {
        Triton.instance.startTransaction();
        Triton.instance.setTemplate(
            Triton.makeBuilder()
                .category(TritonTypes.Category.Integration)
                .type(TritonTypes.Type.Backend)
                .area(TritonTypes.Area.RestAPI)
        );
        
        try {
            RestRequest req = RestContext.request;
            RestResponse res = RestContext.response;
            
            // Process request
            String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
            
            Triton.instance.log(
                Triton.instance.fromTemplate()
                    .summary('REST API call received')
                    .details('accountId=' + accountId + ', method=GET')
            );
            
            // ... process request ...
            
            Triton.instance.log(
                Triton.instance.fromTemplate()
                    .summary('REST API call completed')
                    .details('statusCode=200')
            );
            
        } catch (Exception e) {
            Triton.instance.log(
                Triton.instance.fromTemplate()
                    .exception(e)
                    .summary('REST API call failed')
            );
            throw e;
        } finally {
            Triton.instance.stopTransaction();
        }
    }
}

Scheduled Jobs

global class DataSyncScheduler implements Schedulable {
    global void execute(SchedulableContext sc) {
        Triton.instance.startTransaction();
        Triton.instance.setTemplate(
            Triton.makeBuilder()
                .category(TritonTypes.Category.Apex)
                .type(TritonTypes.Type.Backend)
                .area(TritonTypes.Area.Integration)
        );
        
        try {
            Triton.instance.log(
                Triton.instance.fromTemplate()
                    .summary('Data sync scheduled job started')
            );
            
            // ... sync logic ...
            
            Triton.instance.log(
                Triton.instance.fromTemplate()
                    .summary('Data sync scheduled job completed')
            );
            
        } catch (Exception e) {
            Triton.instance.log(
                Triton.instance.fromTemplate()
                    .exception(e)
                    .summary('Data sync scheduled job failed')
            );
            throw e;
        } finally {
            Triton.instance.stopTransaction();
        }
    }
}

Best Practices Summary

  1. Start transactions early: Begin at the entry point of each context

  2. Set templates consistently: Use templates for all logging within a scope

  3. Log at boundaries: Entry, milestones, decision points, errors, exit

  4. Handle errors properly: Always log exceptions and flush immediately

  5. Use appropriate levels: INFO for milestones, DEBUG for details, ERROR for failures

  6. Include context: Related objects, duration, operation details

  7. Stop transactions: Always end transactions at natural boundaries

By following these implementation patterns, you'll create comprehensive logging coverage across all your Salesforce automation and code.

Last updated