Salesforce Debug & Troubleshooting Specialist
You are a Salesforce debugging expert. Diagnose issues from debug logs, governor limit violations, exceptions, and performance bottlenecks. Provide root-cause analysis and actionable fixes.
1. Debug Log Analysis
Log Levels (from most to least verbose)
| Level |
Use Case |
| FINEST |
Full trace — variable values, internal framework calls |
| FINER |
Detailed flow — method entries/exits with parameters |
| FINE |
Key decision points and loop iterations |
| DEBUG |
General diagnostic information |
| INFO |
High-level transaction milestones |
| WARN |
Recoverable issues that may indicate problems |
| ERROR |
Failures requiring immediate attention |
Log Categories
| Category |
What It Captures |
Apex_code |
Apex execution, System.debug() output, variable assignments |
Apex_profiling |
Cumulative resource usage — SOQL, DML, CPU, heap |
Database |
SOQL queries, DML operations, query plans, row counts |
System |
System methods, platform events, formula evaluations |
Validation |
Validation rules, workflow field updates |
Workflow |
Workflow rules, process builder, flow executions |
Callout |
HTTP callouts, SOAP calls, external service responses |
Visualforce |
VF page rendering, view state, controller actions |
NBA |
Next Best Action strategy execution |
Reading Debug Logs — Key Line Prefixes
EXECUTION_STARTED / EXECUTION_FINISHED — transaction boundaries
CODE_UNIT_STARTED / CODE_UNIT_FINISHED — trigger, class, or method execution
SOQL_EXECUTE_BEGIN / SOQL_EXECUTE_END — query with row count
DML_BEGIN / DML_END — DML operation with row count
EXCEPTION_THROWN — exception type and message
FATAL_ERROR — unrecoverable error with stack trace
HEAP_ALLOCATE — heap memory allocation
LIMIT_USAGE_FOR_NS — governor limit summary per namespace
CUMULATIVE_LIMIT_USAGE — end-of-transaction limit summary
USER_DEBUG — System.debug() output
VARIABLE_SCOPE_BEGIN / VARIABLE_ASSIGNMENT — variable tracking (FINEST)
METHOD_ENTRY / METHOD_EXIT — method call tracking (FINER+)
FLOW_START_INTERVIEWS — flow/process builder execution
VALIDATION_RULE — validation rule evaluation
CALLOUT_REQUEST / CALLOUT_RESPONSE — external HTTP calls
Log Structure
A debug log follows this sequence:
EXECUTION_STARTED — transaction begins
CODE_UNIT_STARTED — trigger or entry point fires
- Before-trigger logic (validation, field updates)
- DML execution and after-trigger logic
- Workflow rules, process builder, flows
- Re-evaluation of before/after triggers if workflow causes field updates
- Commit or rollback
CUMULATIVE_LIMIT_USAGE — final governor limit summary
EXECUTION_FINISHED — transaction ends
2. Governor Limit Monitoring
Limits Class Methods — Check Before Hitting Walls
// SOQL
System.debug('SOQL queries: ' + Limits.getQueries() + ' / ' + Limits.getLimitQueries());
// DML
System.debug('DML statements: ' + Limits.getDmlStatements() + ' / ' + Limits.getLimitDmlStatements());
System.debug('DML rows: ' + Limits.getDmlRows() + ' / ' + Limits.getLimitDmlRows());
// CPU
System.debug('CPU time (ms): ' + Limits.getCpuTime() + ' / ' + Limits.getLimitCpuTime());
// Heap
System.debug('Heap size (bytes): ' + Limits.getHeapSize() + ' / ' + Limits.getLimitHeapSize());
// Query rows
System.debug('Query rows: ' + Limits.getQueryRows() + ' / ' + Limits.getLimitQueryRows());
// Callouts
System.debug('Callouts: ' + Limits.getCallouts() + ' / ' + Limits.getLimitCallouts());
// Future calls
System.debug('Future calls: ' + Limits.getFutureCalls() + ' / ' + Limits.getLimitFutureCalls());
// Queueable jobs
System.debug('Queueable jobs: ' + Limits.getQueueableJobs() + ' / ' + Limits.getLimitQueueableJobs());
When to Check Limits
- Before expensive operations — query or DML in a loop you cannot refactor immediately
- After processing batches — at the end of each batch in
Database.Batchable.execute()
- In utility/service classes — log limits at entry and exit for profiling
- In catch blocks — when a LimitException might be approaching
- Never in tight loops —
Limits.*() calls themselves consume CPU
Sync vs Async Limits
| Resource |
Synchronous |
Asynchronous (Batch/Future/Queueable) |
| SOQL queries |
100 |
200 |
| DML statements |
150 |
150 |
| CPU time |
10,000 ms |
60,000 ms |
| Heap size |
6 MB |
12 MB |
| Query rows |
50,000 |
50,000 |
| Callouts |
100 |
100 |
| DML rows |
10,000 |
10,000 |
3. Common Error Diagnosis
| Error |
Likely Cause |
Fix Direction |
UNABLE_TO_LOCK_ROW |
Concurrent updates on same record or parent record in master-detail |
Retry with FOR UPDATE, reduce batch scope, use async processing, avoid updating parent records unnecessarily |
ENTITY_IS_DELETED |
DML on a record that was deleted earlier in the same transaction or by another user |
Check isDeleted before DML, handle concurrency with try/catch, verify trigger order |
FIELD_CUSTOM_VALIDATION_EXCEPTION |
Validation rule failure |
Check validation rules on the object, ensure field values meet all criteria, use Database.insert(records, false) for partial success |
INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY |
Missing access to a related record (lookup/master-detail parent, owner, queue) |
Verify sharing rules, check OWD, ensure running user has access to related records, use without sharing only with explicit justification |
MIXED_DML_OPERATION |
DML on setup object (User, Group) and non-setup object in same transaction |
Move one DML to @future, use System.runAs() in tests, separate into different transactions |
System.LimitException: Too many SOQL queries |
More than 100 SOQL queries in synchronous transaction |
Move queries out of loops, use collections and Maps for lookups, use SOQL for-loops for large datasets |
System.LimitException: Too many DML statements |
More than 150 DML statements in transaction |
Collect records into Lists, perform bulk DML outside loops |
System.CalloutException |
HTTP callout failure — timeout, invalid endpoint, certificate issue |
Check Named Credential config, verify endpoint URL, handle timeout with retry, check remote site settings |
System.NullPointerException |
Accessing method/property on a null reference |
Add null checks before access, use safe navigation operator ?., verify SOQL returns results before accessing |
System.QueryException: List has no rows |
[SELECT ... LIMIT 1] returned no rows assigned to single sObject variable |
Use List<SObject> and check .isEmpty(), or wrap in try/catch |
System.QueryException: List has more than 1 row |
Query assigned to single variable returned multiple rows |
Add LIMIT 1 or use List<SObject>, investigate data — duplicates may indicate a data quality issue |
CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY |
Trigger recursion or cascading trigger failure |
Implement static recursion guard, check trigger handler framework for re-entrancy protection |
System.AsyncException |
Too many async jobs enqueued, or chaining limit hit |
Check Limits.getQueueableJobs(), use Finalizer for batch chaining, limit enqueue to 1 per Queueable |
System.SerializationException |
Unserializable object in Queueable or Platform Event |
Remove transient references, avoid SObject types with relationship fields in serialized state |
STRING_TOO_LONG |
Field value exceeds maximum length |
Validate or truncate with .abbreviate(maxLength) before DML |
Error Diagnosis Workflow
- Read the full error message — Salesforce errors follow
STATUS_CODE: message format
- Find the originating line — look for
Class.MethodName: line X, column Y in stack trace
- Identify the trigger context — is this before/after insert/update? Check
CODE_UNIT_STARTED
- Check for cascading failures — one trigger failure can cause
CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY in a parent trigger
- Reproduce with minimal data — use Execute Anonymous or a focused test method
4. Debug Log CLI Commands
Tail Logs in Real Time
# Stream logs as they are generated (colored output)
sf apex tail log --target-org myOrg --color
# Tail with specific log level
sf apex tail log --target-org myOrg --debug-level MyDebugLevel
List and Retrieve Logs
# List recent debug logs
sf apex log list --target-org myOrg --json
# Get a specific log by ID
sf apex log get --log-id 07Lxxxxxxxxxxxxxxx --target-org myOrg
# Get the most recent log
sf apex log get --number 1 --target-org myOrg
# Get logs and save to file for analysis
sf apex log get --log-id 07Lxxxxxxxxxxxxxxx --target-org myOrg > debug.log
Run Apex with Debug Output
# Execute anonymous Apex and capture output
sf apex run --target-org myOrg --file scripts/debug-script.apex
# Run inline Apex for quick debugging
echo "System.debug(Limits.getQueries());" | sf apex run --target-org myOrg
Delete Old Logs
# Clean up old logs to free storage
sf apex log list --target-org myOrg --json | \
sf data delete bulk --sobject ApexLog --file -
5. Checkpoint & Developer Console Debugging
Execute Anonymous Debugging
Use Execute Anonymous for targeted investigation:
// Reproduce an issue with specific data
Account testAcc = [SELECT Id, Name, Industry FROM Account WHERE Id = '001xxxxxxxxxxxx'];
System.debug('Account state: ' + JSON.serializePretty(testAcc));
// Test a specific method in isolation
MyService service = new MyService();
try {
service.processRecord(testAcc);
System.debug('SUCCESS: Method completed without error');
} catch (Exception e) {
System.debug('FAILED: ' + e.getTypeName() + ' - ' + e.getMessage());
System.debug('Stack trace: ' + e.getStackTraceString());
}
// Check governor limits after operation
System.debug('Post-execution SOQL: ' + Limits.getQueries());
System.debug('Post-execution DML: ' + Limits.getDmlStatements());
System.debug('Post-execution CPU: ' + Limits.getCpuTime() + 'ms');
Checkpoints (Developer Console)
- Set checkpoints on specific lines in Developer Console
- Checkpoints capture heap state, local variables, and static variables at that execution point
- Maximum 5 checkpoints active at a time
- Checkpoints expire after 30 minutes
- Results appear in the Checkpoint Inspector tab
- Use checkpoints when System.debug() is insufficient — they capture the full object graph
SOQL Query Debugging in Developer Console
Query Editor → Execute SOQL/SOSL directly
Logs tab → Filter by "DATABASE" events to see query performance
Query Plan tool → Use Tooling API: /services/data/vXX.0/query?explain=SELECT ...
6. Performance Profiling
Identifying CPU Bottlenecks
Look for these patterns in debug logs:
METHOD_ENTRY / METHOD_EXIT — calculate time between pairs
- High
CUMULATIVE_LIMIT_USAGE CPU time relative to the operation size
HEAP_ALLOCATE in large amounts inside loops
Common Performance Anti-Patterns
| Anti-Pattern |
Log Signal |
Fix |
| SOQL in loop |
Repeated SOQL_EXECUTE_BEGIN in same code unit |
Query before loop, use Map for lookups |
| DML in loop |
Repeated DML_BEGIN in same code unit |
Collect into List, DML once after loop |
| Large heap allocation |
HEAP_ALLOCATE with large byte counts in loops |
Use SOQL for-loop, process in batches |
| Expensive describe calls |
Repeated Schema.getGlobalDescribe() |
Cache in static variable |
| String concatenation in loop |
Rising heap, CPU time |
Use String.join() or List<String> |
| Unfiltered SOQL |
SOQL_EXECUTE_END with high row count |
Add WHERE filters, use selective indexed fields |
| Nested loops over collections |
High CPU, no SOQL/DML signal |
Use Map-based lookups, reduce O(n^2) to O(n) |
CPU Time Profiling Pattern
Long startCpu = Limits.getCpuTime();
// ... operation under test ...
Long endCpu = Limits.getCpuTime();
System.debug('CPU consumed: ' + (endCpu - startCpu) + 'ms for operation X');
Heap Profiling Pattern
Integer heapBefore = Limits.getHeapSize();
// ... operation under test ...
Integer heapAfter = Limits.getHeapSize();
System.debug('Heap delta: ' + (heapAfter - heapBefore) + ' bytes for operation X');
7. Trace Flags
Setting Up Trace Flags via CLI
# Create a debug level first
sf data create record --sobject DebugLevel --target-org myOrg \
--values "DeveloperName='DetailedDebug' MasterLabel='Detailed Debug' \
ApexCode='FINE' ApexProfiling='FINEST' Database='FINE' System='DEBUG' \
Validation='INFO' Workflow='INFO' Callout='INFO' Visualforce='INFO'"
# Query the debug level ID
sf data query --query "SELECT Id FROM DebugLevel WHERE DeveloperName='DetailedDebug'" \
--target-org myOrg --json
# Create a trace flag for a specific user (lasts up to 24 hours)
sf data create record --sobject TraceFlag --target-org myOrg \
--values "TracedEntityId='005xxxxxxxxxxxx' DebugLevelId='7dlxxxxxxxxxxxx' \
LogType='USER_DEBUG' StartDate='2026-03-20T00:00:00.000Z' \
ExpirationDate='2026-03-20T23:59:59.000Z'"
Trace Flag Types
| LogType |
Traces |
USER_DEBUG |
All transactions by a specific user |
CLASS_TRACING |
Executions involving a specific Apex class |
DEVELOPER_LOG |
Current Developer Console session |
Trace Flag via Setup UI
- Setup > Debug Logs > New
- Select traced entity (User, Apex Class, Apex Trigger)
- Set start/end time (max 24 hours)
- Select debug level
- Save — logs will be captured until expiration or 20 logs generated (whichever first)
8. Gotchas
Debug Log Truncation
- Debug logs are truncated at 20 MB — large transactions will lose the beginning of the log
- The log shows
*** Skipped N bytes of detailed log when truncated
- To avoid: reduce log levels on categories you do not need, set non-essential categories to NONE or ERROR
- Truncated logs still include
CUMULATIVE_LIMIT_USAGE at the end
Log Retention
- Debug logs are retained for only 24 hours (or until 20 logs accumulate per trace flag)
- Download critical logs immediately for post-mortem analysis
- Use
sf apex log get to save logs to local files before they expire
Trace Flag Expiry
- Trace flags have a maximum duration of 24 hours
- They silently stop capturing logs after expiration — no warning
- Re-create trace flags before reproducing intermittent issues
- Maximum 250 MB of debug logs per org (oldest are purged first)
Performance Impact of Debugging
System.debug() in Production
- Debug statements are not captured unless a trace flag is active on the running user
- They still consume CPU time regardless of whether a trace flag is set
- Never use
System.debug() with sensitive data (PII, credentials, tokens)
- Prefer custom logging frameworks (Platform Events + Big Objects) for production observability
Other Traps
System.debug() calls toString() on the argument — this can throw NullPointerException if the object graph has null references
- Aggregate queries (
COUNT(), SUM()) consume 1 query row per aggregate result
Database.setSavepoint() and Database.rollback() count as DML statements
- Trigger.new is read-only in after triggers — modifying it throws a runtime error
- Tests with
@isTest(SeeAllData=true) can pass in dev but fail in CI due to data differences
9. Debugging Workflow
Step-by-Step Process
Reproduce the issue
- Identify the exact user action, API call, or automated process that fails
- Note the timestamp window and the user experiencing the issue
Set up trace flags
# Ensure trace flag is active for the user
sf apex tail log --target-org myOrg --color
Trigger the issue and capture the log
- Reproduce via UI, API, or Execute Anonymous
- Save the log immediately:
sf apex log get --number 1 --target-org myOrg > issue.log
Scan for errors first
- Search for
EXCEPTION_THROWN, FATAL_ERROR, and LIMIT_USAGE in the log
- If truncated, focus on
CUMULATIVE_LIMIT_USAGE at the end
Trace the execution path
- Find
CODE_UNIT_STARTED to identify which triggers/classes executed
- Track the order: before triggers, DML, after triggers, workflows, process builder, flows
Check governor limits
- Look at
LIMIT_USAGE_FOR_NS — are any limits above 70%?
- Cross-reference SOQL count with the number of
SOQL_EXECUTE_BEGIN events
Identify the root cause
- Is it a data issue? (missing record, null field)
- Is it a logic issue? (wrong condition, missing bulkification)
- Is it a limits issue? (SOQL in loop, DML in loop)
- Is it a concurrency issue? (record locking, race condition)
- Is it a configuration issue? (validation rule, sharing rule, permission)
Fix and verify
- Apply the smallest correct fix
- Re-run with trace flag active to confirm the issue is resolved
- Check that governor limits improved (not just that the error went away)
Quick Diagnosis Commands
# Search for errors in a saved log
grep -E "EXCEPTION_THROWN|FATAL_ERROR|LIMIT_USAGE" debug.log
# Count SOQL queries in log (look for loops)
grep -c "SOQL_EXECUTE_BEGIN" debug.log
# Count DML operations in log
grep -c "DML_BEGIN" debug.log
# Find slow queries (queries returning many rows)
grep "SOQL_EXECUTE_END" debug.log | grep -E "Rows:[0-9]{3,}"
10. Cross-Skill Integration
| Need |
Delegate to |
Reason |
| Fix Apex code |
sf-apex |
Code change generation and review |
| Write/run tests |
sf-testing |
Test execution, coverage, assertions |
| Deploy fix |
sf-deploy |
Deployment orchestration |
| Data investigation |
sf-data |
Query and inspect org data |
| Security audit |
sf-security |
CRUD/FLS and sharing review |
References
- Debug Reference -- Limits class methods, log parsing patterns, Execute Anonymous patterns, error handling, performance profiling, Tooling API trace flags
- Governor Limits -- per-transaction SOQL, DML, CPU, heap limits
1---2name: sf-debug-23description: Debug and troubleshoot Salesforce applications using debug logs, governor limit monitoring, error diagnosis, and performance profiling. Use when analyzing debug logs, diagnosing governor limit violations, interpreting stack traces, resolving common Salesforce errors, or profiling Apex performance. Activate on .log files, mentions of "debug", "governor limit", "error", "exception", "troubleshoot", "stack trace", or "performance issue".4license: Apache-2.05---6
7# Salesforce Debug & Troubleshooting Specialist
8
9You are a Salesforce debugging expert. Diagnose issues from debug logs, governor limit violations, exceptions, and performance bottlenecks. Provide root-cause analysis and actionable fixes.
10
11## 1. Debug Log Analysis
12
13### Log Levels (from most to least verbose)
14
15| Level | Use Case |
16|-------|----------|
17| FINEST | Full trace — variable values, internal framework calls |
18| FINER | Detailed flow — method entries/exits with parameters |
19| FINE | Key decision points and loop iterations |
20| DEBUG | General diagnostic information |
21| INFO | High-level transaction milestones |
22| WARN | Recoverable issues that may indicate problems |
23| ERROR | Failures requiring immediate attention |
24
25### Log Categories
26
27| Category | What It Captures |
28|----------|-----------------|
29| `Apex_code` | Apex execution, System.debug() output, variable assignments |
30| `Apex_profiling` | Cumulative resource usage — SOQL, DML, CPU, heap |
31| `Database` | SOQL queries, DML operations, query plans, row counts |
32| `System` | System methods, platform events, formula evaluations |
33| `Validation` | Validation rules, workflow field updates |
34| `Workflow` | Workflow rules, process builder, flow executions |
35| `Callout` | HTTP callouts, SOAP calls, external service responses |
36| `Visualforce` | VF page rendering, view state, controller actions |
37| `NBA` | Next Best Action strategy execution |
38
39### Reading Debug Logs — Key Line Prefixes
40
41```
42EXECUTION_STARTED / EXECUTION_FINISHED — transaction boundaries
43CODE_UNIT_STARTED / CODE_UNIT_FINISHED — trigger, class, or method execution
44SOQL_EXECUTE_BEGIN / SOQL_EXECUTE_END — query with row count
45DML_BEGIN / DML_END — DML operation with row count
46EXCEPTION_THROWN — exception type and message
47FATAL_ERROR — unrecoverable error with stack trace
48HEAP_ALLOCATE — heap memory allocation
49LIMIT_USAGE_FOR_NS — governor limit summary per namespace
50CUMULATIVE_LIMIT_USAGE — end-of-transaction limit summary
51USER_DEBUG — System.debug() output
52VARIABLE_SCOPE_BEGIN / VARIABLE_ASSIGNMENT — variable tracking (FINEST)
53METHOD_ENTRY / METHOD_EXIT — method call tracking (FINER+)
54FLOW_START_INTERVIEWS — flow/process builder execution
55VALIDATION_RULE — validation rule evaluation
56CALLOUT_REQUEST / CALLOUT_RESPONSE — external HTTP calls
57```
58
59### Log Structure
60
61A debug log follows this sequence:
621. `EXECUTION_STARTED` — transaction begins
632. `CODE_UNIT_STARTED` — trigger or entry point fires
643. Before-trigger logic (validation, field updates)
654. DML execution and after-trigger logic
665. Workflow rules, process builder, flows
676. Re-evaluation of before/after triggers if workflow causes field updates
687. Commit or rollback
698. `CUMULATIVE_LIMIT_USAGE` — final governor limit summary
709. `EXECUTION_FINISHED` — transaction ends
71
72## 2. Governor Limit Monitoring
73
74### Limits Class Methods — Check Before Hitting Walls
75
76```apex
77// SOQL
78System.debug('SOQL queries: ' + Limits.getQueries() + ' / ' + Limits.getLimitQueries());
79
80// DML
81System.debug('DML statements: ' + Limits.getDmlStatements() + ' / ' + Limits.getLimitDmlStatements());
82System.debug('DML rows: ' + Limits.getDmlRows() + ' / ' + Limits.getLimitDmlRows());
83
84// CPU
85System.debug('CPU time (ms): ' + Limits.getCpuTime() + ' / ' + Limits.getLimitCpuTime());
86
87// Heap
88System.debug('Heap size (bytes): ' + Limits.getHeapSize() + ' / ' + Limits.getLimitHeapSize());
89
90// Query rows
91System.debug('Query rows: ' + Limits.getQueryRows() + ' / ' + Limits.getLimitQueryRows());
92
93// Callouts
94System.debug('Callouts: ' + Limits.getCallouts() + ' / ' + Limits.getLimitCallouts());
95
96// Future calls
97System.debug('Future calls: ' + Limits.getFutureCalls() + ' / ' + Limits.getLimitFutureCalls());
98
99// Queueable jobs
100System.debug('Queueable jobs: ' + Limits.getQueueableJobs() + ' / ' + Limits.getLimitQueueableJobs());
101```
102
103### When to Check Limits
104
105- **Before expensive operations** — query or DML in a loop you cannot refactor immediately
106- **After processing batches** — at the end of each batch in `Database.Batchable.execute()`
107- **In utility/service classes** — log limits at entry and exit for profiling
108- **In catch blocks** — when a LimitException might be approaching
109- **Never in tight loops** — `Limits.*()` calls themselves consume CPU
110
111### Sync vs Async Limits
112
113| Resource | Synchronous | Asynchronous (Batch/Future/Queueable) |
114|----------|------------|---------------------------------------|
115| SOQL queries | 100 | 200 |
116| DML statements | 150 | 150 |
117| CPU time | 10,000 ms | 60,000 ms |
118| Heap size | 6 MB | 12 MB |
119| Query rows | 50,000 | 50,000 |
120| Callouts | 100 | 100 |
121| DML rows | 10,000 | 10,000 |
122
123## 3. Common Error Diagnosis
124
125| Error | Likely Cause | Fix Direction |
126|-------|-------------|---------------|
127| `UNABLE_TO_LOCK_ROW` | Concurrent updates on same record or parent record in master-detail | Retry with `FOR UPDATE`, reduce batch scope, use async processing, avoid updating parent records unnecessarily |
128| `ENTITY_IS_DELETED` | DML on a record that was deleted earlier in the same transaction or by another user | Check `isDeleted` before DML, handle concurrency with try/catch, verify trigger order |
129| `FIELD_CUSTOM_VALIDATION_EXCEPTION` | Validation rule failure | Check validation rules on the object, ensure field values meet all criteria, use `Database.insert(records, false)` for partial success |
130| `INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY` | Missing access to a related record (lookup/master-detail parent, owner, queue) | Verify sharing rules, check OWD, ensure running user has access to related records, use `without sharing` only with explicit justification |
131| `MIXED_DML_OPERATION` | DML on setup object (User, Group) and non-setup object in same transaction | Move one DML to `@future`, use `System.runAs()` in tests, separate into different transactions |
132| `System.LimitException: Too many SOQL queries` | More than 100 SOQL queries in synchronous transaction | Move queries out of loops, use collections and Maps for lookups, use SOQL for-loops for large datasets |
133| `System.LimitException: Too many DML statements` | More than 150 DML statements in transaction | Collect records into Lists, perform bulk DML outside loops |
134| `System.CalloutException` | HTTP callout failure — timeout, invalid endpoint, certificate issue | Check Named Credential config, verify endpoint URL, handle timeout with retry, check remote site settings |
135| `System.NullPointerException` | Accessing method/property on a null reference | Add null checks before access, use safe navigation operator `?.`, verify SOQL returns results before accessing |
136| `System.QueryException: List has no rows` | `[SELECT ... LIMIT 1]` returned no rows assigned to single sObject variable | Use `List<SObject>` and check `.isEmpty()`, or wrap in try/catch |
137| `System.QueryException: List has more than 1 row` | Query assigned to single variable returned multiple rows | Add `LIMIT 1` or use `List<SObject>`, investigate data — duplicates may indicate a data quality issue |
138| `CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY` | Trigger recursion or cascading trigger failure | Implement static recursion guard, check trigger handler framework for re-entrancy protection |
139| `System.AsyncException` | Too many async jobs enqueued, or chaining limit hit | Check `Limits.getQueueableJobs()`, use `Finalizer` for batch chaining, limit enqueue to 1 per Queueable |
140| `System.SerializationException` | Unserializable object in Queueable or Platform Event | Remove transient references, avoid SObject types with relationship fields in serialized state |
141| `STRING_TOO_LONG` | Field value exceeds maximum length | Validate or truncate with `.abbreviate(maxLength)` before DML |
142
143### Error Diagnosis Workflow
144
1451. **Read the full error message** — Salesforce errors follow `STATUS_CODE: message` format
1462. **Find the originating line** — look for `Class.MethodName: line X, column Y` in stack trace
1473. **Identify the trigger context** — is this before/after insert/update? Check `CODE_UNIT_STARTED`
1484. **Check for cascading failures** — one trigger failure can cause `CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY` in a parent trigger
1495. **Reproduce with minimal data** — use Execute Anonymous or a focused test method
150
151## 4. Debug Log CLI Commands
152
153### Tail Logs in Real Time
154```bash
155# Stream logs as they are generated (colored output)
156sf apex tail log --target-org myOrg --color
157
158# Tail with specific log level
159sf apex tail log --target-org myOrg --debug-level MyDebugLevel
160```
161
162### List and Retrieve Logs
163```bash
164# List recent debug logs
165sf apex log list --target-org myOrg --json
166
167# Get a specific log by ID
168sf apex log get --log-id 07Lxxxxxxxxxxxxxxx --target-org myOrg
169
170# Get the most recent log
171sf apex log get --number 1 --target-org myOrg
172
173# Get logs and save to file for analysis
174sf apex log get --log-id 07Lxxxxxxxxxxxxxxx --target-org myOrg > debug.log
175```
176
177### Run Apex with Debug Output
178```bash
179# Execute anonymous Apex and capture output
180sf apex run --target-org myOrg --file scripts/debug-script.apex
181
182# Run inline Apex for quick debugging
183echo "System.debug(Limits.getQueries());" | sf apex run --target-org myOrg
184```
185
186### Delete Old Logs
187```bash
188# Clean up old logs to free storage
189sf apex log list --target-org myOrg --json | \
190 sf data delete bulk --sobject ApexLog --file -
191```
192
193## 5. Checkpoint & Developer Console Debugging
194
195### Execute Anonymous Debugging
196
197Use Execute Anonymous for targeted investigation:
198
199```apex
200// Reproduce an issue with specific data
201Account testAcc = [SELECT Id, Name, Industry FROM Account WHERE Id = '001xxxxxxxxxxxx'];
202System.debug('Account state: ' + JSON.serializePretty(testAcc));
203
204// Test a specific method in isolation
205MyService service = new MyService();
206try {
207 service.processRecord(testAcc);
208 System.debug('SUCCESS: Method completed without error');
209} catch (Exception e) {
210 System.debug('FAILED: ' + e.getTypeName() + ' - ' + e.getMessage());
211 System.debug('Stack trace: ' + e.getStackTraceString());
212}
213
214// Check governor limits after operation
215System.debug('Post-execution SOQL: ' + Limits.getQueries());
216System.debug('Post-execution DML: ' + Limits.getDmlStatements());
217System.debug('Post-execution CPU: ' + Limits.getCpuTime() + 'ms');
218```
219
220### Checkpoints (Developer Console)
221
222- Set checkpoints on specific lines in Developer Console
223- Checkpoints capture heap state, local variables, and static variables at that execution point
224- Maximum 5 checkpoints active at a time
225- Checkpoints expire after 30 minutes
226- Results appear in the Checkpoint Inspector tab
227- Use checkpoints when System.debug() is insufficient — they capture the full object graph
228
229### SOQL Query Debugging in Developer Console
230
231```
232Query Editor → Execute SOQL/SOSL directly
233Logs tab → Filter by "DATABASE" events to see query performance
234Query Plan tool → Use Tooling API: /services/data/vXX.0/query?explain=SELECT ...
235```
236
237## 6. Performance Profiling
238
239### Identifying CPU Bottlenecks
240
241Look for these patterns in debug logs:
242- `METHOD_ENTRY` / `METHOD_EXIT` — calculate time between pairs
243- High `CUMULATIVE_LIMIT_USAGE` CPU time relative to the operation size
244- `HEAP_ALLOCATE` in large amounts inside loops
245
246### Common Performance Anti-Patterns
247
248| Anti-Pattern | Log Signal | Fix |
249|-------------|-----------|-----|
250| SOQL in loop | Repeated `SOQL_EXECUTE_BEGIN` in same code unit | Query before loop, use Map for lookups |
251| DML in loop | Repeated `DML_BEGIN` in same code unit | Collect into List, DML once after loop |
252| Large heap allocation | `HEAP_ALLOCATE` with large byte counts in loops | Use SOQL for-loop, process in batches |
253| Expensive describe calls | Repeated `Schema.getGlobalDescribe()` | Cache in static variable |
254| String concatenation in loop | Rising heap, CPU time | Use `String.join()` or `List<String>` |
255| Unfiltered SOQL | `SOQL_EXECUTE_END` with high row count | Add WHERE filters, use selective indexed fields |
256| Nested loops over collections | High CPU, no SOQL/DML signal | Use Map-based lookups, reduce O(n^2) to O(n) |
257
258### CPU Time Profiling Pattern
259
260```apex
261Long startCpu = Limits.getCpuTime();
262// ... operation under test ...
263Long endCpu = Limits.getCpuTime();
264System.debug('CPU consumed: ' + (endCpu - startCpu) + 'ms for operation X');
265```
266
267### Heap Profiling Pattern
268
269```apex
270Integer heapBefore = Limits.getHeapSize();
271// ... operation under test ...
272Integer heapAfter = Limits.getHeapSize();
273System.debug('Heap delta: ' + (heapAfter - heapBefore) + ' bytes for operation X');
274```
275
276## 7. Trace Flags
277
278### Setting Up Trace Flags via CLI
279
280```bash
281# Create a debug level first
282sf data create record --sobject DebugLevel --target-org myOrg \
283 --values "DeveloperName='DetailedDebug' MasterLabel='Detailed Debug' \
284 ApexCode='FINE' ApexProfiling='FINEST' Database='FINE' System='DEBUG' \
285 Validation='INFO' Workflow='INFO' Callout='INFO' Visualforce='INFO'"
286
287# Query the debug level ID
288sf data query --query "SELECT Id FROM DebugLevel WHERE DeveloperName='DetailedDebug'" \
289 --target-org myOrg --json
290
291# Create a trace flag for a specific user (lasts up to 24 hours)
292sf data create record --sobject TraceFlag --target-org myOrg \
293 --values "TracedEntityId='005xxxxxxxxxxxx' DebugLevelId='7dlxxxxxxxxxxxx' \
294 LogType='USER_DEBUG' StartDate='2026-03-20T00:00:00.000Z' \
295 ExpirationDate='2026-03-20T23:59:59.000Z'"
296```
297
298### Trace Flag Types
299
300| LogType | Traces |
301|---------|--------|
302| `USER_DEBUG` | All transactions by a specific user |
303| `CLASS_TRACING` | Executions involving a specific Apex class |
304| `DEVELOPER_LOG` | Current Developer Console session |
305
306### Trace Flag via Setup UI
307
3081. Setup > Debug Logs > New
3092. Select traced entity (User, Apex Class, Apex Trigger)
3103. Set start/end time (max 24 hours)
3114. Select debug level
3125. Save — logs will be captured until expiration or 20 logs generated (whichever first)
313
314## 8. Gotchas
315
316### Debug Log Truncation
317- Debug logs are truncated at **20 MB** — large transactions will lose the beginning of the log
318- The log shows `*** Skipped N bytes of detailed log` when truncated
319- To avoid: reduce log levels on categories you do not need, set non-essential categories to NONE or ERROR
320- Truncated logs still include `CUMULATIVE_LIMIT_USAGE` at the end
321
322### Log Retention
323- Debug logs are retained for only **24 hours** (or until 20 logs accumulate per trace flag)
324- Download critical logs immediately for post-mortem analysis
325- Use `sf apex log get` to save logs to local files before they expire
326
327### Trace Flag Expiry
328- Trace flags have a maximum duration of **24 hours**
329- They silently stop capturing logs after expiration — no warning
330- Re-create trace flags before reproducing intermittent issues
331- Maximum 250 MB of debug logs per org (oldest are purged first)
332
333### Performance Impact of Debugging
334- `System.debug()` statements consume CPU time even in production
335- Writing to the debug log adds overhead — high log levels slow execution
336- Log levels at FINEST can **double** CPU time for complex transactions
337- Remove or guard debug statements before deploying to production:
338 ```apex
339 // Use a custom setting or custom metadata to gate debug output
340 if (DebugSettings__c.getInstance().EnableDetailedLogging__c) {
341 System.debug(LoggingLevel.FINE, 'Detailed: ' + JSON.serialize(records));
342 }
343 ```
344
345### System.debug() in Production
346- Debug statements are **not** captured unless a trace flag is active on the running user
347- They still consume CPU time regardless of whether a trace flag is set
348- Never use `System.debug()` with sensitive data (PII, credentials, tokens)
349- Prefer custom logging frameworks (Platform Events + Big Objects) for production observability
350
351### Other Traps
352- `System.debug()` calls `toString()` on the argument — this can throw NullPointerException if the object graph has null references
353- Aggregate queries (`COUNT()`, `SUM()`) consume 1 query row per aggregate result
354- `Database.setSavepoint()` and `Database.rollback()` count as DML statements
355- Trigger.new is read-only in after triggers — modifying it throws a runtime error
356- Tests with `@isTest(SeeAllData=true)` can pass in dev but fail in CI due to data differences
357
358## 9. Debugging Workflow
359
360### Step-by-Step Process
361
3621. **Reproduce the issue**
363 - Identify the exact user action, API call, or automated process that fails
364 - Note the timestamp window and the user experiencing the issue
365
3662. **Set up trace flags**
367 ```bash
368 # Ensure trace flag is active for the user
369 sf apex tail log --target-org myOrg --color
370 ```
371
3723. **Trigger the issue and capture the log**
373 - Reproduce via UI, API, or Execute Anonymous
374 - Save the log immediately: `sf apex log get --number 1 --target-org myOrg > issue.log`
375
3764. **Scan for errors first**
377 - Search for `EXCEPTION_THROWN`, `FATAL_ERROR`, and `LIMIT_USAGE` in the log
378 - If truncated, focus on `CUMULATIVE_LIMIT_USAGE` at the end
379
3805. **Trace the execution path**
381 - Find `CODE_UNIT_STARTED` to identify which triggers/classes executed
382 - Track the order: before triggers, DML, after triggers, workflows, process builder, flows
383
3846. **Check governor limits**
385 - Look at `LIMIT_USAGE_FOR_NS` — are any limits above 70%?
386 - Cross-reference SOQL count with the number of `SOQL_EXECUTE_BEGIN` events
387
3887. **Identify the root cause**
389 - Is it a data issue? (missing record, null field)
390 - Is it a logic issue? (wrong condition, missing bulkification)
391 - Is it a limits issue? (SOQL in loop, DML in loop)
392 - Is it a concurrency issue? (record locking, race condition)
393 - Is it a configuration issue? (validation rule, sharing rule, permission)
394
3958. **Fix and verify**
396 - Apply the smallest correct fix
397 - Re-run with trace flag active to confirm the issue is resolved
398 - Check that governor limits improved (not just that the error went away)
399
400### Quick Diagnosis Commands
401
402```bash
403# Search for errors in a saved log
404grep -E "EXCEPTION_THROWN|FATAL_ERROR|LIMIT_USAGE" debug.log
405
406# Count SOQL queries in log (look for loops)
407grep -c "SOQL_EXECUTE_BEGIN" debug.log
408
409# Count DML operations in log
410grep -c "DML_BEGIN" debug.log
411
412# Find slow queries (queries returning many rows)
413grep "SOQL_EXECUTE_END" debug.log | grep -E "Rows:[0-9]{3,}"
414```
415
416## 10. Cross-Skill Integration
417
418| Need | Delegate to | Reason |
419|------|-------------|--------|
420| Fix Apex code | [sf-apex](../sf-apex/SKILL.md) | Code change generation and review |
421| Write/run tests | [sf-testing](../sf-testing/SKILL.md) | Test execution, coverage, assertions |
422| Deploy fix | [sf-deploy](../sf-deploy/SKILL.md) | Deployment orchestration |
423| Data investigation | [sf-data](../sf-data/SKILL.md) | Query and inspect org data |
424| Security audit | [sf-security](../sf-security/SKILL.md) | CRUD/FLS and sharing review |
425
426## References
427- [Debug Reference](references/debug-reference.md) -- Limits class methods, log parsing patterns, Execute Anonymous patterns, error handling, performance profiling, Tooling API trace flags
428- [Governor Limits](../../references/governor-limits.md) -- per-transaction SOQL, DML, CPU, heap limits