Salesforce Debug Log Levels Explained
Salesforce debug logs have eight levels: NONE, ERROR, WARN, INFO, DEBUG, FINE, FINER, FINEST, set per category (ApexCode, Database, System, Workflow, and others) in a Debug Level record. A trace flag then applies that debug level to a user, class, or automated process for a time window. Higher levels log more detail but hit the 20 MB truncation cap faster.
Trace flags vs. debug levels: the two-part system
- Debug Level: a named, reusable record (like the default
SFDC_DevConsole) that sets one level per category. - Trace Flag: applies a debug level to a target for a start/expiry window. Targets can be a user, an Apex class or trigger (class-scoped tracing), or the Automated Process entity (for logs from scheduled flows and system automation).
No active trace flag means no log at all. That's the #1 answer to "why is Salesforce not generating debug logs?": the flag expired. (ForceLens's Smart Capture creates the flag for you; see how to read Apex debug logs.)
The constraints that catch people out
Four rules from the TraceFlag specification explain most of the confusion around why logging behaves unexpectedly:
- A trace flag lasts at most 24 hours. The expiration date must be less than 24 hours after the start date. There is no "leave it on permanently" option, so long-running investigations need the flag renewed daily.
- Only one trace flag per entity can be active at a time. You cannot stack two flags on the same user with different debug levels and expect both to apply.
- User flags and class flags interact in a specific way. Salesforce documents that "if there are both user and entity-level flags, the user flags take precedence until a method from a class with an entity trace flag is entered. When the method returns, the user trace flags are restored."
- Class-scoped flags cannot target a user. The
CLASS_TRACINGtype applies to an Apex class or trigger; user-level logging uses a separate flag type.
That third rule is the powerful one, and it is the basis of the best technique on this page. You can run a quiet user-level flag alongside a verbose class-level flag: the transaction logs sparsely everywhere except inside the class you care about, which logs at FINEST. You get statement-level detail exactly where you need it while the rest of the transaction stays small enough to avoid truncation.
This is the single most effective answer to "my log truncates before reaching my bug", and it is underused because the precedence behaviour is not obvious from the Setup UI.
Where to set them
The same two-part system is exposed differently depending on where you work, which is a common source of confusion when instructions do not match what you see on screen.
Setup → Debug Logs. The canonical place. Add a trace flag under User Trace Flags, choose the traced entity and time window, and pick or create a debug level. This is the only interface exposing all trace flag types, including class-scoped and Automated Process flags.
Developer Console. Opening the Console automatically creates a trace flag for you with the SFDC_DevConsole debug level, which is why logs sometimes appear without you configuring anything. Change levels via Debug → Change Log Levels. Convenient, but it only traces you, and closing the Console does not necessarily clear the flag.
VS Code with the Salesforce Extensions. The SFDX: Turn On Apex Debug Log for Replay Debugger command sets a trace flag tuned for replay debugging. Note that it uses its own level configuration, so a log captured this way may not contain what you expected if you were following Setup-based instructions.
Tooling API. For automation, TraceFlag and DebugLevel are both writable objects. Teams that debug regularly often script flag creation rather than clicking through Setup each time.
ForceLens's Smart Capture sits in this list too: it creates the trace flag with appropriate levels, waits for the log, and opens it parsed, removing the round trip through Setup entirely.
What each category controls
| Category | Governs | Notable events it emits |
|---|---|---|
| ApexCode | Apex execution: methods, statements, System.debug | METHOD_ENTRY/EXIT, USER_DEBUG, VARIABLE_ASSIGNMENT (FINEST) |
| ApexProfiling | Profiling summaries, cumulative stats | CUMULATIVE_LIMIT_USAGE, profiling summaries |
| Database | SOQL, SOSL, DML detail | SOQL_EXECUTE_BEGIN/END, DML_BEGIN/END |
| System | Platform internals and system methods | SYSTEM_METHOD_ENTRY/EXIT, very noisy at DEBUG+ |
| Workflow | Workflow rules, Process Builder, Flows | WF_RULE_*, FLOW_ELEMENT_BEGIN/END |
| Validation | Validation rule evaluation | VALIDATION_RULE, VALIDATION_FORMULA |
| Callout | External HTTP/SOAP requests & responses | CALLOUT_REQUEST/RESPONSE |
| Visualforce | VF page lifecycle and serialization | VF_* events |
What the levels actually mean
The levels are cumulative: each one includes everything the levels below it emit, so FINE gives you all of DEBUG's output plus more. Setting a category higher never removes information; it only adds volume, which is why the cost of over-setting is truncation rather than missing events.
| Level | Rule of thumb |
|---|---|
NONE | Category is silent. |
ERROR / WARN | Only failures/warnings. Good for categories you don't care about right now. |
INFO | Key milestones, e.g. Database at INFO logs queries without row-by-row detail. |
DEBUG | The workhorse. ApexCode at DEBUG shows USER_DEBUG (your System.debug output). |
FINE / FINER | Adds method entries/exits and progressively more internal detail. |
FINEST | Everything, including variable assignments in ApexCode. Maximum insight, maximum log size. |
One subtlety: System.debug(LoggingLevel.ERROR, 'msg') tags your statement with a level, so it survives even when ApexCode is turned down. It's a handy trick for permanent diagnostic lines.
Which level do you need for a given event?
Working the other way round is usually more practical: you know which event you are looking for, and you need to know the minimum level that emits it. Setting anything higher only costs you log size.
| You want to see | Category | Recommended level |
|---|---|---|
Your System.debug() output | ApexCode | DEBUG |
| Which triggers and classes ran | ApexCode | INFO |
| Method entry and exit | ApexCode | FINE |
| Variable assignments | ApexCode | FINEST |
| SOQL query text and row counts | Database | INFO |
| DML operations and row counts | Database | INFO |
| Governor limit consumption | ApexProfiling | INFO |
| Which Flow elements executed | Workflow | FINE |
| Flow variable values | Workflow | FINER |
| Validation rule results | Validation | INFO |
| HTTP request and response bodies | Callout | INFO |
These are working recommendations rather than exact thresholds. Salesforce documents the full event-to-level mapping in its debug log details reference, and levels can shift between releases. If an event you expected is missing, raising that one category a single step is usually enough.
Two observations worth internalising. Most of what you need is available at INFO or FINE: the reflex to set everything to FINEST buys very little beyond variable assignments while multiplying log size several times over.
And the expensive settings are specific: ApexCode at FINEST and System at DEBUG or above are what actually fill logs. Raising Database, Validation or Callout costs comparatively little, because those events are far less frequent.
Recommended presets
| Task | ApexCode | Database | Workflow | System | Others |
|---|---|---|---|---|---|
| General Apex debugging | FINEST | INFO | INFO | DEBUG | INFO |
| SOQL investigation | FINE | FINEST | INFO | INFO | ERROR |
| Flow debugging | DEBUG | INFO | FINER | INFO | ERROR |
| Log keeps truncating | DEBUG | INFO | ERROR | INFO | NONE/ERROR |
| Performance / CPU | FINE | INFO | INFO | INFO | ApexProfiling: FINE |
Choosing levels for a real investigation
The presets above are starting points. In practice you adjust based on what the first log fails to show you, and these three scenarios cover most of that adjustment.
"I can see it failed, but not why"
The log names an exception and a line number, but the values that caused it are invisible. Raise ApexCode to FINEST, but scope it to the failing class with a class-level trace flag rather than applying it to your whole user session. Variable assignments appear immediately before the failure, usually answering the question outright.
If the transaction is large enough that even a scoped log truncates, reproduce with a single record instead of a bulk operation. Most logic bugs reproduce at n=1; only limit and bulkification bugs genuinely require volume.
"The record changed and I do not know what changed it"
A field holds an unexpected value after a save. Here you need breadth rather than depth, because the culprit could be Apex, a flow, workflow, or a validation-driven side effect. Set ApexCode: INFO, Workflow: FINE, Validation: INFO, Database: INFO, and leave System low.
This produces a compact log showing every automation that touched the record in order of execution, without the statement-level noise that would bury it. Search the log for the field name; the last write before EXECUTION_FINISHED is the one that stuck.
"It only fails in production"
The hardest case, because verbose logging on a busy production org risks both truncation and exhausting the org's log allocation. Keep levels deliberately low (ApexCode: INFO, ApexProfiling: INFO, everything else ERROR) and rely on the limit summary rather than statement detail.
ApexProfiling at INFO is the important setting here: it gives you LIMIT_USAGE_FOR_NS and cumulative limit data, which is usually what production-only failures come down to. Data volume and competing automation push a transaction over a threshold it clears comfortably in a sandbox, and the limit summary shows that in a few lines rather than a few hundred megabytes.
Set the flag for a narrow window around a known reproduction rather than leaving it running. A verbose flag on an active production user can generate the 1,000 MB that disables trace flags org-wide, taking everyone else's logging down with it.
Beating the 20 MB truncation cap
When a log exceeds 20 MB, Salesforce drops lines and stamps *** Skipped markers. Because it removes lines from the middle rather than the end, the omission is often exactly where your bug was. In order of effectiveness:
- Silence the noise: System → INFO, Validation/Workflow → ERROR when you don't need them.
- Scope to a class: add a class-level trace flag (ForceLens's Advanced Capture does this) so only that class logs at FINEST while the user-level flag stays modest.
- Shrink the transaction: reproduce with 1 record instead of 200 where the bug allows, since most logic bugs reproduce at a single record and only limit or bulkification problems genuinely need volume.
The two-flag technique, step by step
Option 2 above deserves spelling out, because it solves truncation properly rather than trading away the detail you need:
- Create a quiet debug level:
ApexCode: INFO,Database: INFO, everything elseERRORorNONE. - Apply it to your user trace flag. The transaction now logs its skeleton without detail.
- Create a verbose debug level:
ApexCode: FINEST. - Apply that to a class-level trace flag on the specific class or trigger you are investigating.
Because Salesforce restores the user-level flag when a traced class's method returns, you get a compact log of the whole transaction with a high-detail window around exactly the code you care about. A transaction that truncated at 20 MB under blanket FINEST will often come in at a fraction of that.
Logging automated and system processes
User trace flags only capture what that user's session does. Work running under system contexts needs a different target, which is why scheduled jobs and platform event handlers so often produce no log at all.
Set a trace flag on the Automated Process entity to capture scheduled flows, platform event-triggered flows, and other automation running as the system rather than a person. Integration users are a related case: if an external system writes through the API, trace that user, not yourself. The log follows whoever performed the action, not whoever is investigating it.
Keeping levels honest over time
Two habits prevent the slow degradation that leaves an org unable to log anything useful:
Remove trace flags when you are done. Flags expire within 24 hours, but a team that habitually renews verbose flags on active users can generate enough volume to exhaust the org's log allocation. At that point nobody can add a new flag until logs are deleted. On shared sandboxes this is the most common cause of "logging just stopped working".
Keep a small set of named debug levels. Rather than editing one level's settings for every investigation, create a handful of reusable records matching the presets above: one for Apex, one for SOQL, one for Flow, one minimal. Switching a trace flag between named levels takes seconds and avoids the situation where nobody remembers what the current settings were meant to capture.
Whatever levels you pick, the output is still a wall of text. A debug log analyzer like ForceLens turns it into an execution tree, SOQL/DML tabs and a limits dashboard, whatever the verbosity.
Frequently asked questions
What are the debug log levels in Salesforce?
NONE, ERROR, WARN, INFO, DEBUG, FINE, FINER, FINEST, set per category in a Debug Level record, applied by a trace flag.
What's the difference between a trace flag and a debug level?
The debug level defines verbosity per category; the trace flag applies it to a user, class, or automated process for a time window. Without an active trace flag, nothing logs.
Which level shows System.debug output?
ApexCode at DEBUG or higher, unless you tag the statement with an explicit level like LoggingLevel.ERROR, which shows even at lower settings.
How do I stop log truncation?
Lower noisy categories (System, Validation, Workflow), scope high-detail tracing to a single class, or shrink the reproducing transaction. The 20 MB cap itself can't be raised.
How long does a trace flag last?
A maximum of 24 hours: the expiration date must be less than 24 hours after the start date. There is no permanent option, so ongoing investigations need the flag renewed each day. An expired flag is the most common reason logs stop appearing.
Can I have a user trace flag and a class trace flag at the same time?
Yes, and it is the best way to avoid truncation. Salesforce documents that user flags take precedence until a method from a class with an entity trace flag is entered, and the user flags are restored when it returns. Run a quiet user-level level plus a FINEST class-level one to get detail only where you need it.
Which log level shows Flow elements?
The Workflow category, not one named "Flow". FINE shows which elements executed; FINER adds variable values. If you are seeing triggers but no flow events, this naming is almost always why.
Why do my scheduled jobs produce no debug log?
Because a user trace flag only captures that user's session. Scheduled flows, platform event handlers and similar system automation run under the Automated Process entity, which needs its own trace flag. Similarly, API work performed by an integration user requires tracing that user rather than yourself.
Does raising Database to FINEST make logs much bigger?
Far less than raising ApexCode. The expensive settings are ApexCode at FINEST, which logs every variable assignment, and System at DEBUG or above. Database, Validation and Callout events fire much less frequently, so raising those is comparatively cheap.
Set levels in one click
ForceLens's Smart Capture and Advanced Capture create the trace flag with sensible levels for you, then open the captured log analyzed. Free and local.