105

I'm using a lot of qDebug() << statements for debug output. Is there any cross-platform way I can redirect that debug output to a file, without resorting to shell scripts? I'm guessing that open() and dup2() will do the job in Linux, but will it work compiled with MinGW in Windows?

And maybe there is a Qt way to do it?

Nawaz
  • 353,942
  • 115
  • 666
  • 851
Septagram
  • 9,425
  • 13
  • 50
  • 81

7 Answers7

146

You've to install a message handler using qInstallMessageHandler function, and then, you can use QTextStream to write the debug message to a file. Here is a sample example:

#include <QtGlobal>
#include <stdio.h>
#include <stdlib.h>

void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QByteArray localMsg = msg.toLocal8Bit();
    switch (type) {
    case QtDebugMsg:
        fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtInfoMsg:
        fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtWarningMsg:
        fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtCriticalMsg:
        fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtFatalMsg:
        fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        abort();
    }
}

int main(int argc, char **argv)
{
    qInstallMessageHandler(myMessageOutput); // Install the handler
    QApplication app(argc, argv);
    ...
    return app.exec();
}

Taken from the doc of qInstallMessageHandler (I only added the comments):

In the above example, the function myMessageOutput uses stderr which you might want to replace with some other file stream, or completely re-write the function!

Once you write and install this function, all your qDebug (as well as qWarning, qCritical etc) messages would be redirected to the file you're writing to in the handler.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 4
    Hey, thanks a lot. Not only will it let me redirect debug output to a file, it also enables me to print more useful info, like a timestamp :) – Septagram Feb 10 '11 at 06:56
  • 2
    @Septagram: Exactly. You can add some useful messages in the hanlder itself; and you may even output different messages to different files, based on what you use `qDebug`, `qWarning`, `qCritical` and so on! – Nawaz Feb 10 '11 at 06:58
  • 1
    By the way, the callback that does the actual output - void myMessageOutput(QtMsgType type, const char *msg) - in what encoding does it receive a message? – Septagram Feb 16 '11 at 10:08
  • 9
    The documentation links and API have changed a bit. `qInstallMsgHandler` was deprecated and replaced by `qInstallMessageHandler` (same idea) in Qt5. For 5.0 `qInstallMsgHandler` is at http://qt-project.org/doc/qt-5.0/qtcore/qtglobal.html#qInstallMsgHandler and `qInstallMessageHandler` is there as well. For 5.1, `qInstallMsgHandler` was removed entirely. – Jason C Nov 14 '13 at 18:00
  • I found that this code messed with QtCreator "Application Output" window significantly, even when I did stuff like print the message to `stdout` as well. `qInstallMessageHandler` returns the previously installed handler, so you can chain them and keep nice QtCreator behavior. `QtMessageHandler default_msg_handler = qInstallMessageHandler(...` then in your new handler get that previous handler somehow and do `(*default_msg_handler)(type, context, msg);` – Ross Rogers Mar 30 '17 at 00:00
  • Using Qt4.8: I changed `qInstallMesssagHandler()` to `qInstallMsgHandler()`. Any alternative for `QMessageLogContext` in Qt4? – Aditya Jan 24 '18 at 15:11
  • 1
    @Aditya: In Qt4, the callback takes only two arguments. So you can use this: `void myMessageOutput(QtMsgType type, const char *msg) { ... }` – Nawaz Jan 24 '18 at 16:21
29

From here all credit goes to spirit.

#include <QApplication>
#include <QtDebug>
#include <QFile>
#include <QTextStream>

void myMessageHandler(QtMsgType type, const QMessageLogContext &, const QString & msg)
{
    QString txt;
    switch (type) {
    case QtDebugMsg:
        txt = QString("Debug: %1").arg(msg);
        break;
    case QtWarningMsg:
        txt = QString("Warning: %1").arg(msg);
        break;
    case QtCriticalMsg:
        txt = QString("Critical: %1").arg(msg);
        break;
    case QtFatalMsg:
        txt = QString("Fatal: %1").arg(msg);
        abort();
    }
    QFile outFile("log");
    outFile.open(QIODevice::WriteOnly | QIODevice::Append);
    QTextStream ts(&outFile);
    ts << txt << endl;
}

int main( int argc, char * argv[] )
{
    QApplication app( argc, argv );
    qInstallMessageHandler(myMessageHandler);   
    ...
    return app.exec();
}
Paul Masri-Stone
  • 2,843
  • 3
  • 29
  • 51
Sandeep Datta
  • 28,607
  • 15
  • 70
  • 90
  • 1
    case QtFatalMsg:...abort(); // it will quit before write the log – raidsan Oct 02 '12 at 02:11
  • 1
    Start from QT 5, `qInstallMessageHandler` should be used instead of `qInstallMsgHandler` to change message handler. – SuB Oct 23 '16 at 04:23
  • 3
    This message handler is not thread-safe. You will lose log messages if they are sent by two threads at the same time (outFile.open() will return false for one of the threads). You could lock a QMutex before you try to open the file, then unlock the mutex after closing the file. This is the simplest approach but it will introduce thread contention. You'll need to look at low-overhead thread-safe message queuing otherwise... and you might be better using a framework. – Anthony Hayward Sep 05 '18 at 15:21
  • 1
    Nice solution. To avoid overhead of opening the file each time, open the file and instantiate the `QTextStream` within `main()`. And make the `QTextStream` a `static` variable outside the functions. – Paul Masri-Stone Jan 14 '22 at 12:32
  • This version contains a bug: fatal messages are not printed! I've posted an answer to fix that, and also to incorporate @PaulMasri-Stone's suggestions. – Fabio says Reinstate Monica Jul 11 '23 at 18:01
13

Here is a working example of hooking the default message handler.

Thank you @Ross Rogers!

// -- main.cpp

// Get the default Qt message handler.
static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(0);

void myCustomMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    // Handle the messages!

    // Call the default handler.
    (*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
}

int main(int argc, char *argv[])
{
    qInstallMessageHandler(myCustomMessageHandler);

    QApplication a(argc, argv);

    qDebug() << "Wello Horld!";

    return 0;
}
Andrew
  • 1,344
  • 1
  • 12
  • 20
11

Here is a cross-platform solution to log to the console, if app was ran from Qt Creator, and to the debug.log file, when it is compiled and being ran as a standalone app.

main.cpp:

#include <QApplication>
#include <QtGlobal>
#include <QtDebug>
#include <QTextStream>
#include <QTextCodec>
#include <QLocale>
#include <QTime>
#include <QFile>   

const QString logFilePath = "debug.log";
bool logToFile = false;
    
void customMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QHash<QtMsgType, QString> msgLevelHash({{QtDebugMsg, "Debug"}, {QtInfoMsg, "Info"}, {QtWarningMsg, "Warning"}, {QtCriticalMsg, "Critical"}, {QtFatalMsg, "Fatal"}});
    QByteArray localMsg = msg.toLocal8Bit();
    QTime time = QTime::currentTime();
    QString formattedTime = time.toString("hh:mm:ss.zzz");
    QByteArray formattedTimeMsg = formattedTime.toLocal8Bit();
    QString logLevelName = msgLevelHash[type];
    QByteArray logLevelMsg = logLevelName.toLocal8Bit();

    if (logToFile) {
        QString txt = QString("%1 %2: %3 (%4)").arg(formattedTime, logLevelName, msg,  context.file);
        QFile outFile(logFilePath);
        outFile.open(QIODevice::WriteOnly | QIODevice::Append);
        QTextStream ts(&outFile);
        ts << txt << endl;
        outFile.close();
    } else {
        fprintf(stderr, "%s %s: %s (%s:%u, %s)\n", formattedTimeMsg.constData(), logLevelMsg.constData(), localMsg.constData(), context.file, context.line, context.function);
        fflush(stderr);
    }

    if (type == QtFatalMsg)
        abort();
}

int main(int argc, char *argv[])
{
    QByteArray envVar = qgetenv("QTDIR");       //  check if the app is ran in Qt Creator

    if (envVar.isEmpty())
        logToFile = true;

    qInstallMessageHandler(customMessageOutput); // custom message handler for debugging

    QApplication a(argc, argv);
    // ...and the rest of 'main' follows

Log formatting is handled by QString("%1 %2: %3 (%4)").arg... (for the file) and fprintf(stderr, "%s %s: %s (%s:%u, %s)\n"... (for console).

Inspiration: https://gist.github.com/polovik/10714049.

Neurotransmitter
  • 6,289
  • 2
  • 51
  • 38
  • I see that you call "outFile.close()" in every log event. May I omit it? – diverger Mar 24 '18 at 11:25
  • I don't recommend it in this setup, since you're opening log file every time and thus it should be closed. But you can change the algorithm in a way, that log file is being opened only once at the app's init. This way, you'll only need to close it once, when the app's exiting. – Neurotransmitter Mar 25 '18 at 12:20
  • 1
    Thanks! It very helpful. – Aaron Aug 05 '18 at 16:18
  • This message handler is not thread-safe. You will lose log messages if they are sent by two threads at the same time (outFile.open() will return false for one of the threads). You could lock a QMutex before you try to open the file, then unlock the mutex after closing the file. This is the simplest approach but it will introduce thread contention. You'll need to look at low-overhead thread-safe message queuing otherwise... and you might be better using a framework! – Anthony Hayward Sep 05 '18 at 15:21
  • I do agree with you — it's far from perfect. But it does its job most of the time. Anyway, any modifications are welcome! – Neurotransmitter Sep 06 '18 at 17:07
6

Well, I would say that the moment when you need to redirect your debug output to anything different than stderr is when you could think about some logging tool. If you feel you need one I would recommend using QxtLogger ("The QxtLogger class is an easy to use, easy to extend logging tool.") from Qxt library.

Ivan Romanov
  • 1,138
  • 1
  • 9
  • 24
Piotr Dobrogost
  • 41,292
  • 40
  • 236
  • 366
6

Here's a simple, thread safe idiomatic Qt example to log both to stderr and file:

void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message)
{
    static QMutex mutex;
    QMutexLocker lock(&mutex);

    static QFile logFile(LOGFILE_LOCATION);
    static bool logFileIsOpen = logFile.open(QIODevice::Append | QIODevice::Text);

    std::cerr << qPrintable(qFormatLogMessage(type, context, message)) << std::endl;

    if (logFileIsOpen) {
        logFile.write(qFormatLogMessage(type, context, message).toUtf8() + '\n');
        logFile.flush();
    }
}

Install it with qInstallMessageHandler(messageHandler) as described in other answers.

mrts
  • 16,697
  • 8
  • 89
  • 72
  • Shouldn't you close the file after you open it? – Curtwagner1984 May 10 '22 at 05:34
  • 1
    @Curtwagner1984, the QFile destructor closes the file automatically. – mrts May 11 '22 at 15:43
  • @mrts That would be the case if it was non static. – Rudy B Jul 22 '22 at 20:42
  • 1
    @RudyB, nope, destructors for static objects (all objects with static storage, not just local static objects) are called when `main()` exits or when the standard C library function `exit()` is explicitly called. `main()` in most implementations calls `exit()` when it terminates. So all is good. – mrts Jul 25 '22 at 10:11
0

Based on Autodidact's answer, with a few changes:

  • That answer contains a nasty bug: since each case only sets the value of txt, which is printed only after the switch, QtFatalMsg will abort() before printing anything. This means that fatal errors won't be logged! My version fixes it.
  • Instead of opening the file and instantiating the QTextStream every time a message is logged, it sets everything up only once, when the program starts. This reduces the overhead of logging a message, as suggested by Paul Masri-Stone in a comment .
#include <QApplication>
#include <QtDebug>
#include <QFile>
#include <QTextStream>

static QTextStream output_ts;

void myMessageHandler(QtMsgType type, const QMessageLogContext&, const QString& msg)
{
    switch (type) {
    case QtDebugMsg:
        output_ts << QString("Debug: %1").arg(msg) << endl;
        break;
    case QtWarningMsg:
        output_ts << QString("Warning: %1").arg(msg) << endl;
        break;
    case QtCriticalMsg:
        output_ts << QString("Critical: %1").arg(msg) << endl;
        break;
    case QtFatalMsg:
        output_ts << QString("Fatal: %1").arg(msg) << endl;
        abort();
    }
}

int main(int argc, char* argv[])
{
    QString logfilePath = QStringLiteral("C:\\mydir\\log.txt");
    QFile outFile(logfilePath);
    outFile.open(QIODevice::WriteOnly | QIODevice::Append);
    output_ts.setDevice(&outFile);
    qInstallMessageHandler(messageHandler);
    
    QApplication app(argc, argv);
    
    ...
    return app.exec();
}