How to redirect qDebug, qWarning, qCritical etc output?
Asked Answered
A

7

106

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?

Attune answered 10/2, 2011 at 6:42 Comment(0)
G
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.

Gilud answered 10/2, 2011 at 6:48 Comment(7)
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 :)Attune
@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!Gilud
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?Attune
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 qt-project.org/doc/qt-5.0/qtcore/… and qInstallMessageHandler is there as well. For 5.1, qInstallMsgHandler was removed entirely.Belt
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);Bankrupt
Using Qt4.8: I changed qInstallMesssagHandler() to qInstallMsgHandler(). Any alternative for QMessageLogContext in Qt4?Secede
@Aditya: In Qt4, the callback takes only two arguments. So you can use this: void myMessageOutput(QtMsgType type, const char *msg) { ... }Gilud
G
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();
}
Gaffer answered 10/2, 2011 at 6:42 Comment(5)
case QtFatalMsg:...abort(); // it will quit before write the logKigali
Start from QT 5, qInstallMessageHandler should be used instead of qInstallMsgHandler to change message handler.Displant
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.Bharat
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.Despotic
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.Lightship
S
14

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;
}
Suction answered 27/4, 2017 at 8:28 Comment(0)
R
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.

Rigi answered 30/7, 2017 at 13:10 Comment(5)
I see that you call "outFile.close()" in every log event. May I omit it?Mohler
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.Rigi
Thanks! It very helpful.Asberry
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!Bharat
I do agree with you — it's far from perfect. But it does its job most of the time. Anyway, any modifications are welcome!Rigi
D
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.

Derange answered 10/2, 2011 at 19:27 Comment(0)
H
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.

Hypothesis answered 15/6, 2020 at 14:21 Comment(4)
Shouldn't you close the file after you open it?Redmon
@Curtwagner1984, the QFile destructor closes the file automatically.Hypothesis
@Hypothesis That would be the case if it was non static.Trimurti
@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.Hypothesis
L
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();
}
Lightship answered 11/7, 2023 at 17:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.