15

A code posted on Redirecting Output in PyQt does two good things at once: it takes advantage of logging module to nicely format messages and it redirects standard stdout and stderr in to QT QTextBrowser widget. But I would like QTextBrowser to receive all the print output coming out of running code. Particularly I want to redirect the nicely formatted messages that come from logger. An ideal solution would re-direct every logger. output in to QTextBrowser (and not just stdout and stderr). As a matter of fact I would rather redirect logger's messages instead of stdout and stderr ones if I would have to make a choice between the twos.... So here are the commands used to printout formatted messages:

logger.debug('debug message')
logger.info('info message')
logger.warning('warning message')
logger.error('error message')

And here is the code: enter image description here

========

import sys
from PyQt4 import QtCore, QtGui
import logging
logger = logging.getLogger(__name__)

class XStream(QtCore.QObject):
    _stdout = None
    _stderr = None
    messageWritten = QtCore.pyqtSignal(str)
    def flush( self ):
        pass
    def fileno( self ):
        return -1
    def write( self, msg ):
        if ( not self.signalsBlocked() ):
            self.messageWritten.emit(unicode(msg))

    @staticmethod
    def stdout():
        if ( not XStream._stdout ):
            XStream._stdout = XStream()
            sys.stdout = XStream._stdout
        return XStream._stdout

    @staticmethod
    def stderr():
        if ( not XStream._stderr ):
            XStream._stderr = XStream()
            sys.stderr = XStream._stderr
        return XStream._stderr

class MyDialog(QtGui.QDialog):
    def __init__( self, parent = None ):
        super(MyDialog, self).__init__(parent)

        self._console = QtGui.QTextBrowser(self)
        self._button  = QtGui.QPushButton(self)
        self._button.setText('Test Me')

        layout = QtGui.QVBoxLayout()
        layout.addWidget(self._console)
        layout.addWidget(self._button)
        self.setLayout(layout)

        XStream.stdout().messageWritten.connect( self._console.insertPlainText )
        XStream.stderr().messageWritten.connect( self._console.insertPlainText )

        self._button.clicked.connect(self.test)

    def test( self ):
        print 'printing LINE 1'
        print 'printing LINE 2'
        logger.debug('debug message')
        logger.info('info message')
        logger.warning('warning message')
        logger.error('error message')
        # error out something
        print blah

if ( __name__ == '__main__' ):
    # logging.basicConfig()
    # logging.basicConfig(filename='example.log',level=logging.DEBUG)
    logging.basicConfig(level=logging.DEBUG)

    app = None
    if ( not QtGui.QApplication.instance() ):
        app = QtGui.QApplication([])

    dlg = MyDialog()
    dlg.show()

    if ( app ):
        app.exec_()

POSTED LATER::FULLY WORKING EXAMPLE::SOLVED BY Mr.Dano

import sys
from PyQt4 import QtCore, QtGui
import logging

class QtHandler(logging.Handler):
    def __init__(self):
        logging.Handler.__init__(self)
    def emit(self, record):
        record = self.format(record)
        if record: XStream.stdout().write('%s\n'%record)
        # originally: XStream.stdout().write("{}\n".format(record))


logger = logging.getLogger(__name__)
handler = QtHandler()
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)


class XStream(QtCore.QObject):
    _stdout = None
    _stderr = None
    messageWritten = QtCore.pyqtSignal(str)
    def flush( self ):
        pass
    def fileno( self ):
        return -1
    def write( self, msg ):
        if ( not self.signalsBlocked() ):
            self.messageWritten.emit(unicode(msg))
    @staticmethod
    def stdout():
        if ( not XStream._stdout ):
            XStream._stdout = XStream()
            sys.stdout = XStream._stdout
        return XStream._stdout
    @staticmethod
    def stderr():
        if ( not XStream._stderr ):
            XStream._stderr = XStream()
            sys.stderr = XStream._stderr
        return XStream._stderr

class MyDialog(QtGui.QDialog):
    def __init__( self, parent = None ):
        super(MyDialog, self).__init__(parent)

        self._console = QtGui.QTextBrowser(self)
        self._button  = QtGui.QPushButton(self)
        self._button.setText('Test Me')

        layout = QtGui.QVBoxLayout()
        layout.addWidget(self._console)
        layout.addWidget(self._button)
        self.setLayout(layout)

        XStream.stdout().messageWritten.connect( self._console.insertPlainText )
        XStream.stderr().messageWritten.connect( self._console.insertPlainText )

        self._button.clicked.connect(self.test)

    def test( self ):
        logger.debug('debug message')
        logger.info('info message')
        logger.warning('warning message')
        logger.error('error message')
        print 'Old school hand made print message'

if ( __name__ == '__main__' ):
    app = None
    if ( not QtGui.QApplication.instance() ):
        app = QtGui.QApplication([])
    dlg = MyDialog()
    dlg.show()
    if ( app ):
        app.exec_()
Community
  • 1
  • 1
alphanumeric
  • 17,967
  • 64
  • 244
  • 392
  • 1
    is it possible to set the default vertical scrollbar at max position? – Raj Jun 06 '15 at 08:06
  • @Raj Yes. See [this](https://stackoverflow.com/questions/16568451/pyqt-how-to-make-a-textarea-to-write-messages-to-kinda-like-printing-to-a-co) example – Mattwmaster58 Apr 08 '18 at 03:59
  • Unfortunately I am unable to print everything. Stack traces are left out. Is there a way to include them? – wondim Oct 21 '20 at 16:14

2 Answers2

14

You can create a custom logging.Handler and add it to your logger:

import logging
logger = logging.getLogger(__name__)

class QtHandler(logging.Handler):

    def __init__(self):
        logging.Handler.__init__(self)

    def emit(self, record):
        record = self.format(record)
        XStream.stdout().write("{}\n".format(record))

handler = QtHandler()
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

Then remove the logging.basisConfig(level=logging.DEBUG) line in the if __name__ == "__main__": block. You'll see your log messages only appear in your dialog box.

dano
  • 91,354
  • 19
  • 222
  • 219
  • I am getting ValueError: zero length field name in format. Please take a look at the code I posted under 'EDITED' section of my original question.... – alphanumeric Jun 28 '14 at 19:08
  • I replaced `XStream.stdout().write("{}\n".format(record))` with `if record: XStream.stdout().write('%s\n'%record)` and it seems it works like a charm!!! Thanks Dano! You are the man! – alphanumeric Jun 28 '14 at 19:16
  • @Sputnix Ah, sorry, I was using a string formatting feature only available in 2.7. You could replace `{}` with `{0}` and it would work in 2.6. – dano Jun 28 '14 at 19:22
  • I've never used `format` method before. I will give it a try! On a side note, did you notice that `QtGui.QTextBrowser` doesn't scroll down as new lines of the code are being added... It always sits showing a first line of the messages. Do you think it would be possible to make it scroll automatically so the very last line of the code is always visible (and readable)? – alphanumeric Jun 28 '14 at 19:27
  • 8
    FYI, this implementation is not thread safe. The Python logging module is thread safe by default, but this logging handler is not because the same XStream QObject is used no matter what thread you are in. I know it wasn't requested, but should be kept in mind for future use! – three_pineapples Jun 29 '14 at 02:17
  • Awesome, thanks a lot! Does anyone mind trying to explain the code briefly so I can understand why it works? And what it is that is working? – Ben Nov 23 '22 at 14:06
4

The answer given by dano works for 2.7.x, but not for 3.x.

To get the code provided by @dano working in 3.4.3 I had to make the obvious changes to the print statements and also change the write() method in the XStream class from self.messageWritten.emit(unicode(msg)) to self.messageWritten.emit(msg). That unicode call just made the dialog sit there and stare back at me in amusement.

Mogsdad
  • 44,709
  • 21
  • 151
  • 275
Todd Vanyo
  • 545
  • 5
  • 15
  • 1
    I'm tryin to run this with Python 3.x and PySide and your fix is not working for me... It just lists `AttributeError: 'NoneType' object has no attribute 'messageWritten'` for me. – eco May 13 '16 at 15:32
  • 1
    I suggest going to look at this [post](http://stackoverflow.com/questions/28655198/best-way-to-display-logs-in-pyqt/35593078#35593078), which gives a cleaner, and I believe thread safe, answer to this question. I've switched to this because I don't have to worry about the XStream stuff. – Todd Vanyo May 13 '16 at 20:54
  • Thanks a lot! However, I guess I got a minor issue though. I'm receiving almost every output inside the GUI except of this here:" DirectWrite: CreateFontFaceFromHDC() failed (Indicates an error in an input file such as a font file.) for QFontDef(Family="8514oem", pointsize=9, pixelsize=20, styleHint=5, weight=400, stretch=100, hintingPreference=0) LOGFONT("8514oem", lfWidth=0, lfHeight=-20) dpi=120 " I have no idea what this means.. do you? – Ben Nov 23 '22 at 13:59