Legacy Code Logging Rollout

Strategies for rolling out Triton logging in existing Salesforce orgs with legacy automation and code, addressing the challenges of working with undocumented and poorly understood systems.

The Legacy Code Challenge

Legacy code changes should be handled with extreme care, and this guide acknowledges the inherent risks and challenges involved. Unlike greenfield development, legacy systems often suffer from a lack of knowledge and documentation about their functionality. The original developers may be unreachable, tribal knowledge has been lost, and internal teams are poorly equipped to deal with the potential fallout should changes go awry.

When working with legacy code, the primary goal is to add observability without disrupting existing functionality. This requires a careful assessment of the current state, understanding of the team's comfort level with the codebase, and a risk-based approach to implementation.

Understanding Your Legacy Codebase

Before embarking on any logging implementation, assess your team's comfort level with different parts of the codebase:

High Comfort Level

  • Recently added code (within the last 6-12 months)

  • Original author is available and can provide guidance

  • Good documentation exists for the functionality

  • Team has experience with similar patterns in the codebase

  • Comprehensive test coverage exists

Medium Comfort Level

  • Code is 1-3 years old but follows familiar patterns

  • Some documentation exists, though incomplete

  • Team has worked with similar functionality before

  • Partial test coverage exists

  • Original author is reachable but not actively involved

Low Comfort Level

  • Code is 3+ years old with no recent modifications

  • No documentation or outdated documentation

  • Original author is unreachable or no longer with the company

  • No tribal knowledge within the existing team

  • Minimal or no test coverage

  • Complex business logic that's difficult to understand

  • Critical business processes with high impact if broken

Risk-Based Implementation Strategy

Exception Logging Patterns

The most critical aspect of legacy code logging is exception handling. Different patterns require different approaches based on risk assessment:

Pattern A: Non-existent Error Handling (No try/catch)

Current State: Code has no exception handling, errors bubble up naturally.

Risk Level: Low Risk

Recommended Approach:

  • Don't modify the code initially

  • Let automated exception capture via Pharos handle error logging

  • Monitor the automated logs to understand error patterns

  • Only add explicit logging if specific error scenarios need custom handling

Example:

// ❌ Don't modify this initially
public class LegacyAccountProcessor {
    public static void processAccount(Account acc) {
        // No try/catch - let errors bubble up
        update acc;
        processRelatedRecords(acc.Id);
    }
}

// βœ… Let Pharos capture exceptions automatically
// No code changes needed - Pharos will log unhandled exceptions

Pattern B: Some Error Handling but No Logging (or System.debug)

Current State: Code has try/catch blocks but uses System.debug or no logging.

Risk Level: Medium to High Risk

Recommended Approach:

  • Phase 1 (Low Risk): Replace System.debug with Triton logging

  • Phase 2 (Higher Risk): Enhance error handling with more detailed logging

  • Use feature flags to control logging behavior

Example:

// ❌ Current legacy code
public class LegacyIntegrationService {
    public static void callExternalAPI(String endpoint) {
        try {
            HttpRequest req = new HttpRequest();
            req.setEndpoint(endpoint);
            HttpResponse response = new Http().send(req);
            
            if (response.getStatusCode() != 200) {
                throw new CalloutException('API call failed: ' + response.getStatusCode());
            }
        } catch (Exception e) {
            System.debug('Integration error: ' + e.getMessage()); // ❌ Replace this
            throw e;
        }
    }
}

// βœ… Phase 1: Replace System.debug (Low Risk)
public class LegacyIntegrationService {
    public static void callExternalAPI(String endpoint) {
        try {
            HttpRequest req = new HttpRequest();
            req.setEndpoint(endpoint);
            HttpResponse response = new Http().send(req);
            
            if (response.getStatusCode() != 200) {
                throw new CalloutException('API call failed: ' + response.getStatusCode());
            }
        } catch (Exception e) {
            // βœ… Simple replacement - low risk
            if (LoggingSettings__c.getInstance().EnableTritonLogging__c) {
                Triton.instance.log(
                    Triton.makeBuilder()
                        .category(TritonTypes.Category.Integration)
                        .type(TritonTypes.Type.Backend)
                        .area(TritonTypes.Area.ExternalAPI)
                        .exception(e)
                        .summary('External API call failed')
                        .details('endpoint=' + endpoint)
                );
            }
            throw e;
        }
    }
}

// βœ… Phase 2: Enhanced logging (Higher Risk)
public class LegacyIntegrationService {
    public static void callExternalAPI(String endpoint) {
        Long startTime = System.now().getTime();
        
        try {
            HttpRequest req = new HttpRequest();
            req.setEndpoint(endpoint);
            HttpResponse response = new Http().send(req);
            
            if (response.getStatusCode() == 200) {
                // βœ… Log success with context
                if (LoggingSettings__c.getInstance().EnableTritonLogging__c) {
                    Triton.instance.log(
                        Triton.makeBuilder()
                            .category(TritonTypes.Category.Integration)
                            .type(TritonTypes.Type.Backend)
                            .area(TritonTypes.Area.ExternalAPI)
                            .summary('External API call successful')
                            .details('endpoint=' + endpoint + ', statusCode=' + response.getStatusCode())
                            .integrationPayload(req, response)
                            .duration(System.now().getTime() - startTime)
                    );
                }
            } else {
                throw new CalloutException('API call failed: ' + response.getStatusCode());
            }
        } catch (Exception e) {
            // βœ… Enhanced error logging
            if (LoggingSettings__c.getInstance().EnableTritonLogging__c) {
                Triton.instance.log(
                    Triton.makeBuilder()
                        .category(TritonTypes.Category.Integration)
                        .type(TritonTypes.Type.Backend)
                        .area(TritonTypes.Area.ExternalAPI)
                        .exception(e)
                        .summary('External API call failed')
                        .details('endpoint=' + endpoint)
                        .integrationPayload(req, null)
                        .duration(System.now().getTime() - startTime)
                );
            }
            throw e;
        }
    }
}

Pattern C: Error Handling with Custom Logging Framework

Current State: Code uses a custom logging framework (e.g., custom Log class, third-party logging).

Risk Level: Low to High Risk (depending on implementation)

Recommended Approach:

  • Phase 1 (Low Risk): Modify existing logging framework to pass data to Triton

  • Phase 2 (Medium Risk): Replace logging calls if insufficient data is being passed

  • Phase 3 (High Risk): Complete replacement with Triton

Example:

// ❌ Current custom logging framework
public class CustomLogger {
    public static void logError(String message, Exception e) {
        // Custom logging implementation
        Log__c log = new Log__c(
            Message__c = message,
            Error__c = e.getMessage(),
            Timestamp__c = System.now()
        );
        insert log;
    }
}

// βœ… Phase 1: Modify to use Triton (Low Risk)
public class CustomLogger {
    public static void logError(String message, Exception e) {
        // Keep existing logging for backward compatibility
        Log__c log = new Log__c(
            Message__c = message,
            Error__c = e.getMessage(),
            Timestamp__c = System.now()
        );
        insert log;
        
        // βœ… Add Triton logging if enabled
        if (LoggingSettings__c.getInstance().EnableTritonLogging__c) {
            Triton.instance.log(
                Triton.makeBuilder()
                    .category(TritonTypes.Category.Apex)
                    .type(TritonTypes.Type.Backend)
                    .area(TritonTypes.Area.General)
                    .exception(e)
                    .summary(message)
            );
        }
    }
}

// βœ… Phase 2: Enhanced data passing (Medium Risk)
public class CustomLogger {
    public static void logError(String message, Exception e, String context, Id relatedObjectId) {
        // Keep existing logging for backward compatibility
        Log__c log = new Log__c(
            Message__c = message,
            Error__c = e.getMessage(),
            Timestamp__c = System.now(),
            Context__c = context,
            RelatedObjectId__c = relatedObjectId
        );
        insert log;
        
        // βœ… Enhanced Triton logging with more context
        if (LoggingSettings__c.getInstance().EnableTritonLogging__c) {
            Triton.instance.log(
                Triton.makeBuilder()
                    .category(TritonTypes.Category.Apex)
                    .type(TritonTypes.Type.Backend)
                    .area(TritonTypes.Area.General)
                    .exception(e)
                    .summary(message)
                    .details('context=' + context)
                    .relatedObject(relatedObjectId)
            );
        }
    }
}

Implementation Strategy by Comfort Level

High Comfort Level - Aggressive Approach

Timeline: 2-4 weeks Risk Tolerance: High Approach: Direct replacement with comprehensive logging

Strategy:

  1. Week 1: Set up Triton infrastructure and templates

  2. Week 2: Replace System.debug statements with Triton logging

  3. Week 3: Add comprehensive exception handling and context

  4. Week 4: Implement transaction correlation and advanced patterns

Medium Comfort Level - Balanced Approach

Timeline: 4-8 weeks Risk Tolerance: Medium Approach: Gradual replacement with feature flags

Strategy:

  1. Weeks 1-2: Set up infrastructure and create logging templates

  2. Weeks 3-4: Implement feature flags and basic logging replacement

  3. Weeks 5-6: Add exception handling with dual logging (old + new)

  4. Weeks 7-8: Transition to Triton-only logging

Low Comfort Level - Conservative Approach

Timeline: 8-16 weeks Risk Tolerance: Low Approach: Minimal changes with extensive testing

Strategy:

  1. Weeks 1-4: Set up infrastructure and create comprehensive test suite

  2. Weeks 5-8: Implement feature flags and basic exception logging

  3. Weeks 9-12: Add logging to non-critical paths only

  4. Weeks 13-16: Gradually expand to critical paths with extensive monitoring

Risk Mitigation Strategies

Feature Flags and Dual Logging

// Custom Setting for controlling logging behavior
public class LoggingSettings__c {
    public Boolean EnableTritonLogging__c;
    public Boolean EnableLegacyLogging__c;
    public Boolean EnableDualLogging__c;
    public String LogLevel__c; // DEBUG, INFO, WARNING, ERROR
}

// Safe logging wrapper
public class SafeLogger {
    public static void logError(String message, Exception e, String context) {
        LoggingSettings__c settings = LoggingSettings__c.getInstance();
        
        // Legacy logging (if enabled)
        if (settings.EnableLegacyLogging__c || settings.EnableDualLogging__c) {
            // Existing logging logic
            CustomLogger.logError(message, e);
        }
        
        // Triton logging (if enabled)
        if (settings.EnableTritonLogging__c || settings.EnableDualLogging__c) {
            try {
                Triton.instance.log(
                    Triton.makeBuilder()
                        .category(TritonTypes.Category.Apex)
                        .type(TritonTypes.Type.Backend)
                        .area(TritonTypes.Area.General)
                        .exception(e)
                        .summary(message)
                        .details('context=' + context)
                );
            } catch (Exception loggingError) {
                // Don't let logging errors break the main flow
                System.debug('Logging error: ' + loggingError.getMessage());
            }
        }
    }
}

Comprehensive Testing

// Test class for legacy code changes
@IsTest
public class LegacyCodeLoggingTest {
    @TestSetup
    static void setup() {
        // Create test data
        LoggingSettings__c settings = new LoggingSettings__c(
            EnableTritonLogging__c = true,
            EnableLegacyLogging__c = false,
            EnableDualLogging__c = false
        );
        insert settings;
    }
    
    @IsTest
    static void testLegacyIntegrationWithLogging() {
        // Test that logging doesn't break existing functionality
        Test.startTest();
        
        // Test normal operation
        LegacyIntegrationService.callExternalAPI('https://test.endpoint.com');
        
        // Test error scenarios
        try {
            LegacyIntegrationService.callExternalAPI('https://invalid.endpoint.com');
        } catch (Exception e) {
            // Expected error
        }
        
        Test.stopTest();
        
        // Verify logs were created
        List<pharos__Log__c> logs = [SELECT Id FROM pharos__Log__c WHERE CreatedDate = TODAY];
        System.assert(logs.size() > 0, 'Logs should be created');
    }
}

Success Metrics and Monitoring

Implementation Metrics

  • Code Coverage: Maintain or improve existing test coverage

  • Error Rates: Monitor for any increase in errors after logging changes

  • Performance Impact: Measure execution time before and after changes

  • Log Quality: Track percentage of logs with proper context and taxonomy

Business Metrics

  • MTTR: Mean time to resolution for issues

  • Support Ticket Volume: Reduction in tickets due to better debugging

  • System Reliability: No degradation in system stability

  • Developer Productivity: Faster debugging and issue resolution

Rollback Plan

Immediate Rollback:

// Emergency rollback method
public class LoggingRollback {
    public static void disableTritonLogging() {
        LoggingSettings__c settings = LoggingSettings__c.getInstance();
        settings.EnableTritonLogging__c = false;
        settings.EnableLegacyLogging__c = true;
        update settings;
    }
}

Best Practices for Legacy Migration

Do's

  • βœ… Start with non-critical paths to build confidence

  • βœ… Use feature flags to control logging behavior

  • βœ… Implement dual logging during transition periods

  • βœ… Test extensively before making changes to critical code

  • βœ… Monitor closely after each change

  • βœ… Document all changes for future reference

  • βœ… Have a rollback plan ready for emergencies

Don'ts

  • ❌ Don't modify critical business logic without extensive testing

  • ❌ Don't remove existing logging until Triton is proven stable

  • ❌ Don't make changes during business hours without proper testing

  • ❌ Don't ignore performance impact of logging changes

  • ❌ Don't skip monitoring after implementation

  • ❌ Don't assume all code is safe to modify

By following this risk-based approach, organizations can successfully implement Triton logging in legacy systems while minimizing the risk of disrupting critical business processes. The key is to understand your comfort level with the codebase and choose an implementation strategy that matches your risk tolerance.

Last updated