Debug-log debugging is print-statement debugging with extra steps: set a trace flag, reproduce, fetch the log, search it, add a System.debug where the answer wasn't, deploy, reproduce again. sf apex tail log streams logs live and is the best version of this workflow. But the structural limits — 20 MB truncation, expiring trace flags, one round-trip per new question — don't optimize away. A local runtime turns the same investigation into breakpoints and variable inspection.
How debug logs actually get created
An org writes a debug log only when a trace flag is active for the running user. The trace flag points at a debug level — a bundle of per-category verbosity settings (Apex code, database, callouts, validation…) from ERROR down to FINEST. No trace flag, no log: the bug you just reproduced without one produced nothing to read.
Trace flags expire — by design, since verbose logging is expensive — which is why the second-most-common debugging failure after "no repro" is "reproduced it perfectly, trace flag had lapsed." The sf CLI's answer is tail, which sets up the trace flag for you and streams:
# Stream logs live, with color, applying a debug level
sf apex tail log --color --debug-level SFDC_DevConsole
# Or work with stored logs
sf apex list log
sf apex get log --number 1 # most recent log
sf apex get log --log-id 07LXXXXXXXXXXXXReading one: the skill nobody put on the job description
A raw log at FINEST is thousands of lines of event records — CODE_UNIT_STARTED, SOQL_EXECUTE_BEGIN, VARIABLE_ASSIGNMENT — with your actual clue buried somewhere in the middle. Working developers converge on the same techniques:
- Search for
USER_DEBUGfirst. Your ownSystem.debuglines are the only entries you chose to emit. - Then
EXCEPTION_THROWNandFATAL_ERROR. The failure and its stack, if the log kept them. - Tune the debug level down, not up.
FINESTon the database category will bury you and hit the size cap faster. Start atDEBUGfor Apex andINFOelsewhere; raise one category at a time.
The size cap is the trap: logs are limited to 20 MB, and past it the platform drops detail or truncates. On a busy trigger stack under bulk load, the moment you're debugging is often the moment the log is too big to hold — the interesting part gets cut precisely because it was interesting.
The loop you're actually in
Notice the shape of a debug-log investigation. Every new question — "what's in that map right here?" — costs a full cycle: add a System.debug, deploy, reproduce, fetch, search. Five questions, five round-trips, each a few minutes. This is print-statement debugging, the technique the rest of the industry treats as a fallback, institutionalized as the primary interface.
Salesforce's own answers acknowledge the gap. The Apex Replay Debugger replays a debug log as if stepping through it — genuinely useful, but it inherits every log limitation: truncated log, truncated replay, and heap inspection limited to what the log recorded (a detailed comparison). The Apex Interactive Debugger sets real breakpoints in a sandbox, but it's a paid add-on with session limits, and most developers have never seen it running.
What debugging looks like without the org in the way
All of the above follows from one premise: the code runs somewhere you can't attach to. Nimbus drops that premise — your Apex executes locally, so it can host an actual debugger, wired into VS Code and IntelliJ through the standard debug protocol:
- Breakpoints in Apex — in classes, triggers, and test methods. Execution stops; you look around.
- Variable inspection — the real contents of that map, expandable in the editor, not a
String.valueOfyou remembered to add. - Step in / over / out through your code, including into the trigger stack a DML statement fires.
- No trace flags, no expiry, no size cap — there's no log mediating between you and the running program.
# Run a test under the debugger, hit your breakpoint
nimbus test "InvoiceServiceTest.testProration" --debugThe five-questions investigation becomes one run: hit the breakpoint, answer all five by looking. System.debug output still prints — instantly, in your terminal — for the cases where a print is genuinely the right tool. And for the bugs that only exist in a real org (org data, managed packages, platform config), the log workflow above remains the tool; the point is to stop paying its cost for the bugs that are just your code.
Checklist
- No trace flag, no log —
sf apex tail loghandles the flag and streams live. - Search
USER_DEBUGandEXCEPTION_THROWNfirst; read sequentially never. - Tune levels per category, downward.
FINEST-everything mostly buys truncation. - Count your round-trips. Each new question costing a deploy is the real expense.
- Use a real debugger for code-shaped bugs — save the log workflow for org-shaped ones.
Breakpoints in your Apex, today
Nimbus runs your Apex locally and ships a full debugger — breakpoints, stepping, variable inspection — in VS Code and IntelliJ. No trace flags anywhere.