2

After replacing sys.stdout with a Tee logger (to redirect output to a file), PDB no longer works properly. For example, pressing the up arrow produces ^[[A instead of the previous command.

The problem can be reproduced using this snippet:

import sys
import pdb

class Tee(object):
    def __init__(self, name, mode):
        self.file = open(name, mode)
        self.stdout = sys.stdout
        sys.stdout = self
    def __del__(self):
        sys.stdout = self.stdout
        self.file.close()
    def write(self, data):
        self.file.write(data)
        self.stdout.write(data)
    def flush(self):
        self.file.flush()

sys.stdout = Tee('test.txt', 'w')
pdb.set_trace()

Is there any way to replace sys.stdout without breaking PDB?

Bai Li
  • 1,108
  • 1
  • 16
  • 25
  • 1
    Once you replace `stdout` with the tee, it is no longer a terminal, and PDB's readline cannot support any interactivity except typing characters. Logically it is reasonable: you can't edit output both on the screen and in the tee'd file. I would suggest changing the code that writes to stdout so that it wrote to a given file instead, and tee _that_ file, so that you could see it both on the console and in a hard-copy file. – 9000 Jul 05 '19 at 19:26
  • The latter is impractical for me because that would involve changing every print into a custom log function in a large codebase. I'm not sure what you mean by "it's no longer a terminal" -- can you elaborate? – Bai Li Jul 06 '19 at 02:38

1 Answers1

1

(1) You cannot have interactivity on the tee'd output because it's a regular stream, and not a terminal. A terminal allows to do a ton of things: position the cursor, erase contents, read keys, echo keypresses to the screen, etc. A regular file on a disk can't do all these things, this is why pdb fails to do these things in interactive mode. You can check that sys.stdout.isatty() returns True is you run a REPL.

(2) You can of course change every print function invocation in your code to write to stdout and to whatever file you want, because you can redefine print. This works in Python 3, and in Python 2.7 if you from __future__ import print. Then you can do things like this:

system_print = print  # preserve the original.

def print_also_to(other_file):
  def tee_print(*args, **kwargs):
    system_print(*args, **kwargs)  # Normally prints to stdout.  
    system_print(*args, **kwargs, file=other_file)  # Write a copy.
  return tee_print

print = print_also_to(open('/tmp/copy-of-stdout'))  # A crude example.

print("Hello world!")  # Unmodified code.

With print statements, your situation is worse. Use strace on Linux or DTrace on macOS to capture the writing to stdout (and elsewhere) and redirect it to a file when starting your process:

strace -e trace=write -o writes.txt python your-script.py

It will write to the file something like write(1, 'Hello world!'). You would need to parse it and reconstruct output to stdout (1) specifically to produce a real copy of the output.

I suppose pdb's interactive mode will work under this, too; at the very least, Python REPL works fine under strace.

9000
  • 39,899
  • 9
  • 66
  • 104