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:
Week 1: Set up Triton infrastructure and templates
Week 2: Replace System.debug statements with Triton logging
Week 3: Add comprehensive exception handling and context
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:
Weeks 1-2: Set up infrastructure and create logging templates
Weeks 3-4: Implement feature flags and basic logging replacement
Weeks 5-6: Add exception handling with dual logging (old + new)
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:
Weeks 1-4: Set up infrastructure and create comprehensive test suite
Weeks 5-8: Implement feature flags and basic exception logging
Weeks 9-12: Add logging to non-critical paths only
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