Print debugging
The oldest tool still earns its keep. When you cannot attach a debugger to a build agent, a container, or a race that only shows at full speed, a print statement reports what actually ran. Done carelessly it drowns the signal and leaks into the commit. Done well it is a fast, honest trace you can diff.
Method
- Tag every line so it is greppable. Print
>>> parse:42 rate=with the value, never a bare number. When ten lines scroll past, an untagged0.0tells you nothing about where it came from. - Print the value and its type together.
print("qty", qty, type(qty)). Half of all print-debugging surprises are a string where you expected an integer, and the type gives it away instantly. - Sit prints on boundaries, not at random. Log each value entering and leaving the suspect function. Right on entry and wrong on exit means the bug lives inside that function, and you have bracketed it in two lines.
- Make the output diffable. Emit one key-value pair per line in a stable
order, run the good case and the bad case, and
diffthe two logs. The first differing line is where behavior forks. - Prefix a sentinel that never appears in real code. Start each debug line
with a token like
ZZDEBUG.grep ZZDEBUGfinds them in the output, and the same search guarantees you can remove every one later. - Flush, and use stderr when order matters. Buffered stdout can reorder or
swallow lines when the program crashes. Print to stderr with
flush=Trueso the last line before a segfault actually reaches you. - Delete them the instant the bug is found. Grep the sentinel and strip every hit before committing. A stray debug print in shared code is noise for the next reader and a data leak in production logs.
Litmus tests
- Can you name the source line behind each output line from its tag alone?
- Do the good-run and bad-run logs diff cleanly to one first divergence?
- Does
grepfor your sentinel return zero hits before you commit?
Boundaries
For stepping through call stacks, inspecting live objects, or stopping only on a condition, a real debugger is faster: see debugger-fluency. Prints meant to survive as intentional observability belong behind a logging framework, not left as raw stdout.