How to Redirect Logger Output into PyQt Text Widget
Asked Answered
H

2

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_()
Hortense answered 28/6, 2014 at 18:16 Comment(3)
is it possible to set the default vertical scrollbar at max position?Harryharsh
@Harryharsh Yes. See this examplePuddle
Unfortunately I am unable to print everything. Stack traces are left out. Is there a way to include them?Keenankeene
M
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.

Manouch answered 28/6, 2014 at 18:47 Comment(6)
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....Hortense
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!Hortense
@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.Manouch
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)?Hortense
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!Nevski
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?Kelley
H
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.

Hardy answered 29/11, 2015 at 4:43 Comment(3)
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.Gesualdo
I suggest going to look at this post, 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.Hardy
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?Kelley

© 2022 - 2024 — McMap. All rights reserved.