Batu Lab NotesPractical developer guides

Capture printed diagnostics with redirect_stderr

By Batu · English technical notes

Capture printed diagnostics with redirect_stderr

How do you capture printed diagnostics with redirect_stderr? Put the call that writes to sys.stderr inside with redirect_stderr(StringIO()), then read the buffer after the block. In this synthetic validator, the failing row returns False and prints exactly bad row 2 to stderr. Without that boundary, the diagnostic reaches the surrounding test runner's stderr stream.

The corrected branch redirects stderr to captured_stderr. A separate stdout buffer is only an observation point: it verifies the validator did not write normal output. The assertions check both channels precisely—captured_stderr.getvalue() is "bad row 2\n", and normal_stdout.getvalue() is empty. Printing the summary happens after the redirects have ended, so it is the script's deliberate observable output, not validator output.

redirect_stderr temporarily replaces the process-global sys.stderr; it was added in Python 3.5. This makes it useful for small command-line utilities and tightly scoped tests of code that is already hard-wired to print(..., file=sys.stderr). It is not a general isolation boundary for threaded or library code, and it cannot capture a child process's output. If the validator can be redesigned, returning structured diagnostics may be easier to compose than printing them. Python contextlib documentation

AI assistance disclosure: This article was drafted with AI assistance and checked against the cited documentation and a synthetic example.

import sys
from contextlib import redirect_stderr, redirect_stdout
from io import StringIO


def validate(row):
    if row != "ok":
        print("bad row 2", file=sys.stderr)
        return False
    return True


captured_stderr = StringIO()
normal_stdout = StringIO()
with redirect_stdout(normal_stdout):
    with redirect_stderr(captured_stderr):
        accepted = validate("broken")

assert accepted is False
assert captured_stderr.getvalue() == "bad row 2\n"
assert normal_stdout.getvalue() == ""

print(f"captured_stderr={captured_stderr.getvalue()!r}")
print(f"normal_stdout={normal_stdout.getvalue()!r}")
captured_stderr='bad row 2\n'
normal_stdout=''