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 such as 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, and the interesting part gets cut precisely because it was interesting.
Every new question costs a deploy
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. It is 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 and 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, and no size cap, because 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; never read sequentially. - 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, and save the log workflow for org-shaped ones.
Breakpoints in your Apex
Nimbus runs your Apex locally and ships a full debugger (breakpoints, stepping, variable inspection) in VS Code and IntelliJ, with no trace flags.