sdbbs
sdbbs

Reputation: 5492

Python log both ("tee") to terminal and file, and real-time flushing with PyQt5?

First of all, my platform:

$ for ix in "uname -s" "python3 --version"; do echo "$ix: " $($ix); done
uname -s:  MINGW64_NT-10.0-19045
python3 --version:  Python 3.11.9

I would like in my PyQt5 application to have a logfile, where all terminal loging printouts to stdout or stderr would be save; I came up with this example test.py:

#!/usr/bin/env python

import sys, os

# based on https://stackoverflow.com/q/14906787
class LoggerFile(object):
  # "a" would append: here we want to restart log each time
  log = open("logfile.log", "w")
  def __init__(self):
    self.terminal = None
    self.id = None
  def write(self, message):
    self.terminal.write(message)
    self.log.write(message)
  def flush(self):
    # > this flush method is needed for python 3 compatibility.
    # > this handles the flush command by doing nothing.
    # > you might want to specify some extra behavior here.
    # MUST have this, to have logfile populated when running under PyQt5!
    # But even then - logfile is populated only after application exits!
    self.terminal.flush()
    self.log.flush()
class LoggerOut(LoggerFile):
  def __init__(self):
    self.terminal = sys.stdout
    self.id = "out"
class LoggerErr(LoggerFile):
  def __init__(self):
    self.terminal = sys.stderr
    self.id = "err"
# REMEMBER TO ASSIGN!:
sys.stdout = LoggerOut()
sys.stderr = LoggerErr()


from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton

class MainWindow(QMainWindow):
  def __init__(self):
    super().__init__()
    print("Hello from MainWindow")
    self.setWindowTitle("My App")
    button = QPushButton("Press me!")
    button.clicked.connect(self.button_clicked)
    self.setCentralWidget(button)
  def button_clicked(self, s):
    print("click", s)

def main(argv=None):
  print("Hello from main")
  app = QApplication(sys.argv)
  window = MainWindow()
  window.show()
  app.exec()

if __name__ == '__main__':
  main()

Once the "logfile.log" has been created, I can follow its changes in realtime by running tail -f logfile.log in another terminal.

So, this is what happens when I run python3 test.py from a terminal; I get these printouts immedately:

$ python3 test.py
Hello from main
Hello from MainWindow

... and the window starts up:

GUI window

... and at this time, I can see from tail -f logfile.log the following:

$ tail -f logfile.log
...
tail: logfile.log: file truncated

Good, so a new empty file got created, as desired - but notice no printouts yet.

Then I click on the button a few times - the python3 terminal shows printouts immediately/in real-time:

$ python3 test.py
Hello from main
Hello from MainWindow
click False
click False

... however the tail -f logfile.log still prints nothing!

Finally, I close the window - the python3 command exits in its terminal, and only afterwards, do we see the printout in the tail -f logfile.log terminal:

tail: logfile.log: file truncated
Hello from main
Hello from MainWindow
click False
click False

It's as if PyQt5 buffers all stdout/stderr output until the application exits - specifically for the log file writing (but not for terminal streams, stdout/stderr writing) !? And that, in spite of the explicit flush in the LoggerFile class?

(EDIT: moving the reassignments sys.stdout = LoggerOut() after window = MainWindow() has no effect on this file buffering behavior)

So, is it possible - and if so, how - to get such a "tee"-d log printout output to both terminal (stdout/stderr) and to file, which is unbuffered (i.e. writes are executed as soon as possible) for both terminal and file printouts?

Upvotes: 0

Views: 26

Answers (0)

Related Questions