Feed Icon RSS 1.0 XML Feed available

The Essential Noisy Debug Hook for Qt

Date: 2-Oct-2012/22:52

Tags: ,

Characters: (none)

If you're using Qt, you might find that it can often be rather quiet about important and common programming errors. For instance: by default, it considers the inability to connect a signal and a slot--due perhaps to a typographic error--to be a warning worth only quietly pushing into your debug output window. It's easy to miss, especially if you (or libraries you call) have a lot of monitoring information turned on!
I'm the sort who immediately bumps up every warning level in the compiler to turn them into errors. In a similar vein, I've found it useful to throw the following code into the main.cpp of any Qt GUI app I am writing. (Thanks to commenter kkoehne for feedback!)
#ifdef QT_DEBUG

#include <QMessageBox>
#include <QThread>
#include <iostream>

// By default, fairly big problems like QObject::connect not working due to not being able
// to find a signal or slot goes to the debug output.  There can be a lot of spew which
// makes that easy to miss.  While perhaps the release build would want to try and
// keep going, it helps debugging to get told this ASAP.
//
// Would be nice to chain to the default Qt platform error handler
// However, this is not feasible as there is no "default error handler" function
// The default error handling is merely what runs in qt_message_output
//
//     http://qt.gitorious.org/qt/qt/blobs/4.5/src/corelib/global/qglobal.cpp#line2004
//
void noisyFailureMsgHandler(QtMsgType type, const char * msgAsCstring) {
    QString msg (msgAsCstring);
    std::cerr << msgAsCstring;
    std::cerr.flush();

    // Why on earth didn't Qt want to make failed signal/slot connections qWarning?
    if ((type == QtDebugMsg)
            && msg.contains("::connect")) {
        type = QtWarningMsg;
    }

    // this is another one that doesn't make sense as just a debug message.  pretty serious
    // sign of a problem
    // http://www.developer.nokia.com/Community/Wiki/QPainter::begin:Paint_device_returned_engine_%3D%3D_0_(Known_Issue)
    if ((type == QtDebugMsg)
            && msg.contains("QPainter::begin")
            && msg.contains("Paint device returned engine")) {
        type = QtWarningMsg;
    }

    // This qWarning about "Cowardly refusing to send clipboard message to hung application..."
    // is something that can easily happen if you are debugging and the application is paused.
    // As it is so common, not worth popping up a dialog.
    if ((type == QtWarningMsg)
            && QString(msg).contains("QClipboard::event")
            && QString(msg).contains("Cowardly refusing")) {
        type = QtDebugMsg;
    }

    // only the GUI thread should display message boxes.  If you are
    // writing a multithreaded application and the error happens on
    // a non-GUI thread, you'll have to queue the message to the GUI
    QCoreApplication * instance = QCoreApplication::instance();
    const bool isGuiThread = 
        instance && (QThread::currentThread() == instance->thread());

    if (isGuiThread) {
        QMessageBox messageBox;
        switch (type) {
        case QtDebugMsg:
            return;
        case QtWarningMsg:
            messageBox.setIcon(QMessageBox::Warning);
            messageBox.setInformativeText(msg);
            messageBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
            break;
        case QtCriticalMsg:
            messageBox.setIcon(QMessageBox::Critical);
            messageBox.setInformativeText(msg);
            messageBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
            break;
        case QtFatalMsg:
            messageBox.setIcon(QMessageBox::Critical);
            messageBox.setInformativeText(msg);
            messageBox.setStandardButtons(QMessageBox::Cancel);
            break;
        }

        int ret = messageBox.exec();
        if (ret == QMessageBox::Cancel)
            abort();
    } else {
        if (type != QtDebugMsg)
            abort(); // be NOISY unless overridden!        
    }
}
#endif
Then in the main() function, after the QApplication is initialized...I'll say:
#ifdef QT_DEBUG
    // Because our "noisy" message handler uses the GUI subsystem for message
    // boxes, we can't install it until after the QApplication is constructed.  But it
    // is good to be the very next thing to run, to start catching warnings ASAP.
    {
        QtMsgHandler oldMsgHandler (qInstallMsgHandler(noisyFailureMsgHandler));
        Q_UNUSED(oldMsgHandler); // squash "didn't use" compiler warning
    }
#endif
By re-prioritizing the default error levels to something more reasonable and being "noisier" when a serious problem is happening during debugging, it has been a huge time saver. Try it and let me know what you think!
Business Card from SXSW
Copyright (c) 2007-2018 hostilefork.com

Project names and graphic designs are All Rights Reserved, unless otherwise noted. Software codebases are governed by licenses included in their distributions. Posts on blog.hostilefork.com are licensed under the Creative Commons BY-NC-SA 4.0 license, and may be excerpted or adapted under the terms of that license for noncommercial purposes.