Overview

This comprehensive logging framework provides a unified way to log messages, errors, and debugging information across all Salesforce platforms including Apex, Triggers, Flows, Lightning Web Components (LWC), and Aura Components.

Features

  • Universal Coverage: Works across Apex, Triggers, Flows, LWC, and Aura
  • Configurable Log Levels: DEBUG, INFO, WARN, ERROR, FATAL
  • Transaction Tracking: Groups related logs with unique transaction IDs
  • Buffer Management: Optimises performance with intelligent batching
  • Rich Context: Captures user, record, source, and additional metadata
  • Performance Monitoring: Built-in performance logging capabilities
  • Error Handling: Comprehensive error logging with stack traces

Setup Instructions

1. Create Custom Object: Application_Log__c

Create a custom object with the following fields:

Field NameTypeLengthDescription
Level__cPicklistValues: DEBUG, INFO, WARN, ERROR, FATAL
Source__cText255Source platform (Apex, Flow, Trigger, LWC, Aura)
Component_Name__cText255Class.Method name or Flow name
Message__cLong Text Area32,768Log message content
Stack_Trace__cLong Text Area32,768Stack trace for errors
User__cLookupUser who triggered the log
Record_Id__cText18Related record ID
Transaction_Id__cText255Unique transaction identifier
Additional_Data__cLong Text Area32,768JSON formatted additional data

2. Deploy Apex Classes

Deploy the following Apex classes:

  • Logger.cls – Main logging utility
  • TriggerLogger.cls – Trigger-specific logging helper
  • FlowLogger.cls – Flow invocable methods
  • LoggerController.cls – Controller for LWC/Aura

3. Create Lightning Web Component

Create a Lightning Web Component named logger with the logging utility JavaScript.

4. Set Permissions

Grant the following permissions to users:

  • Read/Write access to Application_Log__c object
  • Execute access to LoggerController class
  • Access to FlowLogger invocable methods

Usage Examples

Apex Classes

public class ExampleUsage {
    
    public void demonstrateLogging() {
        // Simple logging
        Logger.info('Process started');
        Logger.debug('ExampleUsage.demonstrateLogging', 'Debug information');
        
        try {
            // Some business logic
            performBusinessLogic();
            Logger.info('ExampleUsage.demonstrateLogging', 'Business logic completed successfully');
        } catch (Exception ex) {
            Logger.error('ExampleUsage.demonstrateLogging', ex);
            Logger.fatal('ExampleUsage.demonstrateLogging', 'Critical error occurred');
        } finally {
            Logger.flush(); // Ensure logs are written
        }
    }
    
    private void performBusinessLogic() {
        // Simulate some processing
        Logger.debug('ExampleUsage.performBusinessLogic', 'Starting business logic');
        
        // Simulate an error
        throw new CalloutException('Simulated error for demonstration');
    }
}

Triggers

trigger AccountTrigger on Account (before insert, before update, after insert, after update) {
    
    TriggerLogger.logTriggerStart('Account', String.valueOf(Trigger.operationType));
    
    try {
        if (Trigger.isBefore && Trigger.isInsert) {
            for (Account acc : Trigger.new) {
                Logger.debug('AccountTrigger', 'Processing new account: ' + acc.Name);
                // Trigger logic here
            }
        }
        
        TriggerLogger.logTriggerEnd('Account', String.valueOf(Trigger.operationType), Trigger.size());
        
    } catch (Exception ex) {
        TriggerLogger.logTriggerError('Account', String.valueOf(Trigger.operationType), ex);
        Logger.flush();
        throw ex;
    }
    
    Logger.flush();
}

Lightning Web Components

<template>
    <lightning-card title="Logger Test Component" icon-name="custom:custom14">
        <div class="slds-m-around_medium">

            <lightning-button-group>
                <lightning-button label="Test Debug Log" onclick={handleDebugLog} class="slds-m-right_small">
                </lightning-button>
                <lightning-button label="Test Info Log" onclick={handleInfoLog} class="slds-m-right_small">
                </lightning-button>
                <lightning-button label="Test Error Log" onclick={handleErrorLog} class="slds-m-right_small">
                </lightning-button>
                <lightning-button label="Load Data" onclick={handleLoadData} class="slds-m-right_small">
                </lightning-button>
                <lightning-button label="Simulate Error" onclick={handleSimulateError} variant="destructive">
                </lightning-button>
            </lightning-button-group>

            <template if:true={data}>
                <div class="slds-m-top_medium">
                    <h3>Data Loaded:</h3>
                    <template for:each={data} for:item="item">
                        <p key={item.id}>{item.name}</p>
                    </template>
                </div>
            </template>

            <template if:true={error}>
                <div class="slds-m-top_medium">
                    <lightning-formatted-text value={error} class="slds-text-color_error">
                    </lightning-formatted-text>
                </div>
            </template>
        </div>
    </lightning-card>
</template>
import { LightningElement, api, track } from 'lwc';
import logger from 'c/logger';

export default class ExampleLogComponent extends LightningElement {
    @api recordId;
    @track data = [];
    @track error;

    async connectedCallback() {
        // Initialize logger with component name
        logger.setComponentName('ExampleLogComponent');
        
        // Log component initialization
        await logger.info('Component initialized', this.recordId, {
            timestamp: new Date().toISOString(),
            userAgent: navigator.userAgent
        });
    }

    disconnectedCallback() {
        // Log component destruction
        logger.logSync('INFO', 'Component disconnected', this.recordId);
    }

    async handleDebugLog() {
        await logger.debug('Debug button clicked', this.recordId, {
            buttonType: 'debug',
            clickTime: new Date().toISOString()
        });
    }

    async handleInfoLog() {
        await logger.info('Info button clicked', this.recordId, {
            buttonType: 'info',
            clickTime: new Date().toISOString()
        });
    }

    async handleErrorLog() {
        await logger.error('Manual error log triggered', this.recordId, {
            buttonType: 'error',
            clickTime: new Date().toISOString()
        });
    }

    async handleLoadData() {
        const startTime = performance.now();
        this.error = null;
        
        try {
            await logger.debug('Starting data load operation', this.recordId);
            
            // Simulate API call
            const result = await this.fetchData();
            this.data = result;
            
            const endTime = performance.now();
            await logger.logPerformance('Data Load', endTime - startTime, { 
                recordCount: result.length,
                recordId: this.recordId
            });
            
            await logger.info('Data loaded successfully', this.recordId, { 
                recordCount: result.length,
                loadTime: endTime - startTime
            });
            
        } catch (error) {
            await logger.logError(error, 'handleLoadData', this.recordId);
            this.error = error.message;
        }
    }

    async handleSimulateError() {
        try {
            await logger.warn('About to simulate an error', this.recordId);
            
            // Intentionally throw an error
            throw new Error('This is a simulated error for testing logging');
            
        } catch (error) {
            await logger.logError(error, 'handleSimulateError', this.recordId);
            this.error = error.message;
        }
    }

    async fetchData() {
        // Simulate async data loading
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (Math.random() > 0.8) {
                    reject(new Error('Simulated API error - network timeout'));
                } else {
                    resolve([
                        { id: '1', name: 'Item 1' },
                        { id: '2', name: 'Item 2' },
                        { id: '3', name: 'Item 3' }
                    ]);
                }
            }, 1000);
        });
    }

    // Error handler for unexpected errors
    errorCallback(error, stack) {
        logger.logSync('FATAL', `Unexpected component error: ${error.message}`, this.recordId, {
            stack: stack,
            component: 'ExampleLogComponent',
            timestamp: new Date().toISOString()
        });
    }
}

Flows

  1. Add an Action element to your Flow
  2. Search for “Log Message”
  3. Configure inputs:
    • Flow Name: “My_Flow_Name”
    • Level: “INFO”
    • Message: “Flow step completed successfully”
    • Record Id{!recordId} (optional)

Configuration Options

Log Levels

Configure the minimum log level by modifying MIN_LOG_LEVEL In the Logger class:

private static final LogLevel MIN_LOG_LEVEL = LogLevel.INFO_LEVEL; // Only INFO and above

Buffer Size

Adjust buffer size for performance optimisation:

private static final Integer MAX_BUFFER_SIZE = 100; // Increase for better performance

Enable/Disable Logging

Control logging programmatically:

Logger.disable(); // Turn off all logging
Logger.enable();  // Turn on logging

Advanced Features

Transaction Tracking

All logs within a single transaction are automatically grouped with a unique transaction ID. You can also set custom transaction IDs:

Logger.setTransactionId('BATCH_JOB_001');
Logger.info('Custom transaction started');

Performance Logging

Track method execution times:

public void performanceExample() {
    Long startTime = System.currentTimeMillis();
    
    // Your business logic here
    performComplexOperation();
    
    Long endTime = System.currentTimeMillis();
    Logger.info('MyClass.performanceExample', 'Operation completed in ' + (endTime - startTime) + 'ms');
}

Structured Logging with Additional Data

Log complex data structures:

Map<String, Object> contextData = new Map<String, Object>{
    'batchSize' => 100,
    'retryCount' => 3,
    'environment' => 'Production'
};

Logger.info('BatchProcessor.execute', 'Batch processing started', JSON.serialize(contextData));

Error Correlation

Link errors to specific records for easier troubleshooting:

try {
    processAccount(accountId);
} catch (Exception ex) {
    Logger.error('AccountProcessor.processAccount', ex.getMessage());
    // Log additional context with the record ID
    Logger.error('AccountProcessor.processAccount', 'Failed to process account: ' + accountId);
}

Best Practices

1. Use Appropriate Log Levels

  • DEBUG: Detailed information for debugging (development only)
  • INFO: General information about application flow
  • WARN: Warning messages about potential issues
  • ERROR: Error conditions that don’t stop execution
  • FATAL: Critical errors that may cause system failure

2. Include Context Information

Always include relevant context:

Logger.info('OrderProcessor.processOrder', 
    'Processing order for customer: ' + customerId + 
    ', Order ID: ' + orderId + 
    ', Amount:  + orderAmount);

3. Flush Logs Appropriately

Flush logs at logical boundaries:

// In batch jobs
Logger.flush(); // After each batch

// In triggers
Logger.flush(); // At the end of trigger execution

// In long-running processes
if (Math.mod(processedCount, 100) == 0) {
    Logger.flush(); // Every 100 records
}

4. Handle Sensitive Data

Avoid logging sensitive information:

// DON'T do this
Logger.debug('User password: ' + password);

// DO this instead
Logger.debug('User authentication attempted for: ' + username);

5. Use Meaningful Messages

Write clear, actionable log messages:

// Poor message
Logger.error('Error occurred');

// Good message
Logger.error('AccountTrigger.validateAccount', 
    'Account validation failed: Missing required field Industry for Account: ' + accountId);

Monitoring and Reporting

Create Reports and Dashboards

  1. Error Trend Report: Track error frequency over time
  2. Performance Dashboard: Monitor system performance metrics
  3. User Activity Report: Track user actions and patterns
  4. Component Health Dashboard: Monitor component-specific issues

Sample SOQL Queries

-- Get all errors from last 24 hours
SELECT Id, Level__c, Source__c, Class_Method__c, Message__c, CreatedDate, User__r.Name 
FROM Application_Log__c 
WHERE Level__c = 'ERROR' AND CreatedDate = LAST_N_DAYS:1
ORDER BY CreatedDate DESC

-- Get transaction logs
SELECT Id, Level__c, Message__c, CreatedDate 
FROM Application_Log__c 
WHERE Transaction_Id__c = 'your_transaction_id'
ORDER BY CreatedDate ASC

-- Performance analysis
SELECT Class_Method__c, COUNT(Id) LogCount, AVG(CreatedDate) AvgTime
FROM Application_Log__c 
WHERE Message__c LIKE '%completed in%'
GROUP BY Class_Method__c

Automated Monitoring

Set up Process Builder or Flow to:

  • Send email alerts for FATAL errors
  • Create cases for recurring errors
  • Notify administrators of system issues

Troubleshooting

Common Issues

  1. Logs not appearing: Check object permissions and field-level security
  2. Performance issues: Reduce buffer size or increase flush frequency
  3. Too many logs: Implement log level filtering in production
  4. Missing context: Ensure transaction IDs are properly set

Debug Mode

Enable detailed debugging:

// Temporary debug logging
Logger.debug('DetailedClass.complexMethod', 'Variable X value: ' + variableX);
Logger.debug('DetailedClass.complexMethod', 'Loop iteration: ' + i + ', Current record: ' + recordId);

Maintenance

Regular Cleanup

Implement automated cleanup to manage log volume:

// Delete logs older than 90 days
DELETE [SELECT Id FROM Application_Log__c WHERE CreatedDate < LAST_N_DAYS:90];

Archival Historical Data

For compliance, consider archiving logs to external systems before deletion.

Performance Optimization

Monitor and optimize:

  • Log volume vs. system performance
  • Buffer size effectiveness
  • Query performance on log objects

Security Considerations

  1. Field-Level Security: Restrict access to sensitive log data
  2. Sharing Rules: Implement appropriate sharing for log records
  3. Data Classification: Mark log fields according to data sensitivity
  4. Retention Policies: Define and implement log retention policies

Integration with External Systems

Splunk Integration

Export logs to Splunk for advanced analytics:

// Custom callout to send logs to external system
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://your-splunk-instance.com/api');
request.setMethod('POST');
request.setBody(JSON.serialize(logData));

This logging framework provides a robust foundation for monitoring, debugging, and maintaining your Salesforce applications across all platforms. Customise it according to your organisation’s specific needs and compliance requirements.

Code Repo

You can access the code base from the “logger-framework-Salesforce” repo.

Must Read

Uday Bheemarpu
Uday Bheemarpu
Articles: 9

Newsletter Updates

Enter your email address below and subscribe to our newsletter

One comment

Leave a Reply

Your email address will not be published. Required fields are marked *

Discover more from Panther Schools

Subscribe now to keep reading and get access to the full archive.

Continue reading