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
@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 correlationArea: Set to appropriate business domain (e.g.,
Accounts
,Opportunities
)Category: Typically
Flow
for most logging scenariosType: 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
Start transactions early: Begin at the entry point of each context
Set templates consistently: Use templates for all logging within a scope
Log at boundaries: Entry, milestones, decision points, errors, exit
Handle errors properly: Always log exceptions and flush immediately
Use appropriate levels: INFO for milestones, DEBUG for details, ERROR for failures
Include context: Related objects, duration, operation details
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